how do you group a list by date range - c#

I would like to sum the durations in my List by DateRange by either weekly or quarterly.
I am not sure to the best approach for this.
List<Event> events = new List<Event>();
class Event
{
public string EventName {get;set;}
public DateTime date {get;set;}
public double duration {get; set;}
}
I am using the LingBridge library which will allow for lambda expressions in .net-2.0

You are going to need to iterate over your collection using for or foreach or similar.
For Q1 for example:
List<Event> Q1Events = new List<Event>();
foreach (Event e in events)
{
if (e.date.Month >= 1 && e.date.Month <= 3)
Q1Events.Add(e);
}

Here is some 2.0 compatible code to achieve grouping on a date. You can adapt it for grouping on a DateTime property of a class.
List<DateTime> dates = ...
Dictionary<int, IList<DateTime>> groupedDates
= new Dictionary<int, IList<DateTime>>();
foreach (DateTime date in dates)
{
int quarter = (date.Month / 3) + 1;
if (groupedDates.ContainsKey(quarter))
{
groupedDates[quarter].Add(date);
}
else
{
List<DateTime> dateGroup = new List<DateTime>();
dateGroup.Add(date);
groupedDates.Add(quarter, dateGroup);
}
}

This would group it by day of year:
events.GroupBy(e => string.Format("{0}.{1}+", e.date.DayOfYear, e.date.Year);
So, now you just have to figure out the WeekOfYear or QuarterOfYear property of a date and use that as your grouping clause.
For QuarterOfYear, this could look something like this:
events.GroupBy(e => string.Format("{0}.{1}+", (e.date.Month % 4) + 1, e.date.Year);
But for the week, well, that gets more complicated. As far as I recall, there are different ways to start counting weeks in a year. Check NodaTime or some other date library to do that for you...

In place return with dates all together.
event.Sort((x, y) => DateTime.Compare(x.date, y.date));

Related

How to group dates by week and weekday with linq?

I have data from which I should count rows by weeks and weekdays. As result I should get
Starting day of the week, weekday, count of data for that day
I have tried this code:
var GroupedByDate = from r in dsDta.Tables[0].Rows.Cast<DataRow>()
let eventTime = (DateTime)r["EntryTime"]
group r by new
{
WeekStart = DateTime(eventTime.Year, eventTime.Month, eventTime.AddDays(-(int)eventTime.DayOfWeek).Day),
WeekDay = eventTime.DayOfWeek
}
into g
select new
{
g.Key,
g.WeekStart,
g.WeekDay,
LoadCount = g.Count()
};
However, from DateTime(eventTime.Year, ...)
I get an error "C# non-invocable member datetime cannot be used like a method."
What to do differently?
The immediate error is due to you missing the new part from your constructor call. However, even with that, you'd still have a problem as you're using the month and year of the existing date, even if the start of the week is in a previous month or year.
Fortunately, you can simplify it very easily:
group r by new
{
WeekStart = eventTime.AddDays(-(int)eventTime.DayOfWeek)),
WeekDay = eventTime.DayOfWeek
}
Or if eventTime isn't always a date, use eventTime.Date.AddDays(...).
Alternatively, for clarity, you could extract a separate method:
group r by new
{
WeekStart = GetStartOfWeek(eventTime)
WeekDay = eventTime.DayOfWeek
}
...
private static DateTime GetStartOfWeek(DateTime date)
{
// Whatever implementation you want
}
That way you can test the implementation of GetStartOfWeek separately, and also make it more complicated if you need to without it impacting your query.

How to get the smallest DateTime/Value in List of Lists - using LINQ

I have a list of 'events' with each a list of DateTimes,
like:
var events = new List<Event>(){
new Event("A"){Occured = new List<DateTime>{"1.1.2018", "3.3.2018"}},
new Event("B"){Occured = new List<DateTime>{"4.4.2018", "5.5.2018"}}};
how can I find the one oldest DateTime in all events.
I tried to combine cascaded OrderByDescending().First() to find the oldest in list of list ... but no luck
(Please use method syntax for the LINQ ;-) Thanx)
Try SelectMany in order to flatten lists within each event into IEnumerable<DateTime>:
DateTime oldest = events
.SelectMany(e => e.Occured) // Now we have IEnumerable<DateTime>
.Min();
If you want an event with the oldest (minimum) date you can Aggregate:
var oldestEvent = events
.Where(s => s.Ocurred.Any()) // To be on the safe side
.Aggregate((s, a) => s.Ocurred.Min() < a.Ocurred.Min() ? s : a);
this will help you
DateTime smallest_date = events.First().Occured.First();
foreach (var eve in events)
{
eve.Occured.Sort();
var temp = eve.Occured.First();
if (temp.CompareTo(smallest_date) < 0)
{
smallest_date = temp;
}
}
Console.WriteLine(smallest_date);

What is the best way to make a DropDown list of Thursdays in C#?

I am new to C# and would like to make a dropdown list containing all Thursday Dates?
I currently have an SQL table with all of these dates, but would rather have them generated in a function and used to populate a dropdown.
Any examples of best approach to this?
Update: I ended up using a calendar bootstrap-datepicker and disabled all days except Thursday’s. This gave me the current month Thursday’s and solved my issue.
You could do something like that where you hard code the first Thursday you want to display, and then set how many Thursdays you want.
var list = new List<DateTime>();
DateTime firstThursday = new DateTime(2018,02,20);
var numberOfThursdayWanted = 1000;
for (int i = 0; i < numberOfThursdayWanted; ++i)
{
list.Add(firstThursday.AddDays(i*7));
}
return list;
You can use LINQ to generate a list of "all":
DateTime firstThursday = DateTime.MinValue.AddDays(Enumerable.Range(0, 7).First(d => DateTime.MinValue.AddDays(d).DayOfWeek == DayOfWeek.Thursday));
int weeks = (int)Math.Ceiling((DateTime.MaxValue - firstThursday).Days / 7.0);
var allThursdays = Enumerable.Range(0, weeks).Select(d => firstThursday.AddDays(d * 7));
Note that allThursdays is just the LINQ query. If doesn't make sense to store all DateTimes in a collection. But maybe you want only those between a specific timespan, f.e.:
DateTime start = DateTime.Today.AddYears(-10);
DateTime end = DateTime.Today.AddYears(10);
DateTime[] allThursDaysInLast10YearsUntilNext10Years = allThursdays
.Where(d => d >= start && d <= end)
.ToArray();
First you need to somehow narrow your results. For example by year, or by count as Dimitri suggests.
For that you can check this answer: Create an array or List of all dates between two dates
Once you have your IEnumerable<DateTime> you just have to use LINQ to filter the thurdays as follows:
IEnumerable<DateTime> allDateTimes = null; //change this for whatever range you need
IEnumerable<DateTime> onlyThursdays = allDateTimes.Where(d => d.DayOfWeek == DayOfWeek.Thursday);

Counting the number of times a value appears in an CSV FIle using C#

I have a CSV File that i want to filter something like this
Example of my CSV File:
Name,LastName,Date
David,tod,09/09/1990
David,lopez,09/09/1994
David,cortez,09/09/1994
Maurice,perez,09/09/1980
Maurice,ruiz,09/09/1996
I want to know, How many people were born between date 1 (01/01/1990) and date 2 (01/01/1999) (with datetimepicker)
And the datagridview should it show something like this:
Name,Frecuency
David,3
Maurice,1
I dont know how do it with compare dates, but I have this code with linq logic
DataTable dtDataSource = new DataTable();
dtDataSource.Columns.Add("Name");
dtDataSource.Columns.Add("Frecuency");
int[] array = new int[10];
array[0] = 1;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 1;
array[5] = 2;
array[6] = 1;
array[7] = 1;
array[8] = 2;
array[9] = 3;
var group = from i in array
group i by i into g
select new
{
g.Key,
Sum = g.Count()
};
foreach (var g in group)
{
dtDataSource.Rows.Add(g.Key,g.Sum);
}
if (dtDataSource != null)
{
dataGridViewReporte.DataSource = dtDataSource;
}
Thanks!
The best, and easiest, way to work with dates in .NET is with the DateTimeOffset structure. This type exposes several methods for parsing dates (which makes converting the date strings from your CSV file easy), and also enables simple comparisons between dates with the standard operators.
See the DateTimeOffset documentation on MSDN.
Note: .NET also has a DateTime structure. I would encourage you to use DateTimeOffset wherever possible, as it helps prevent time zone bugs from creeping in to your code.
Simple Example
As a simple example, this code demonstrates how you can parse a string to a DateTimeOffset in .NET, and then compare it to another date.
// Static property to get the current time, in UTC.
DateTimeOffset now = DateTimeOffset.UtcNow;
string dateString = "09/09/1990";
DateTimeOffset date;
// Use TryParse to defensively parse the date.
if (DateTimeOffset.TryParse(dateString, out date))
{
// The date is valid; we can use a standard operator to compare it.
if (date < now)
{
Console.WriteLine("The parsed date is in the past.");
}
else
{
Console.WriteLine("The parsed date is in the future.");
}
}
Using LINQ
The key element you were missing from your sample code was a Where clause in the LINQ expression. Now that we've seen how to parse dates, it's simply a matter of comparing them to the start and end dates you care about.
.Where(p => p.BirthDate >= startDate && p.BirthDate <= endDate)
Note: I've found that LINQ expressions are really nice to work with when they're strongly typed to some object. I've included a simple Person class in this example, which hopefully clears up the code a lot. This should be fine for most cases, but do keep in mind that LINQ-to-Objects, while incredibly productive, is not always the most efficient solution when you have a lot of data.
The Person class:
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTimeOffset BirthDate { get; set; }
}
Example code:
// Representing the CSV file as an array of strings.
var csv = new []
{
"Name,LastName,Date",
"David,tod,09/09/1990",
"David,lopez,09/09/1994",
"David,cortez,09/09/1994",
"Maurice,perez,09/09/1980",
"Maurice,ruiz,09/09/1996"
};
// Parse each line of the CSV file into a Person object, skipping the first line.
// I'm using DateTimeOffset.Parse for simplicity, but production code should
// use the .TryParse method to be defensive.
var people = csv
.Skip(1)
.Select(line =>
{
var parts = line.Split(',');
return new Person
{
FirstName = parts[0],
LastName = parts[1],
BirthDate = DateTimeOffset.Parse(parts[2]),
};
});
// Create start and end dates we can use to compare.
var startDate = new DateTimeOffset(year: 1990, month: 01, day: 01, hour: 0, minute: 0, second: 0, offset: TimeSpan.Zero);
var endDate = new DateTimeOffset(year: 1999, month: 01, day: 01, hour: 0, minute: 0, second: 0, offset: TimeSpan.Zero);
// First, we filter the people by their birth dates.
// Then, we group by their first name and project the counts.
var groups = people
.Where(p => p.BirthDate >= startDate && p.BirthDate <= endDate)
.GroupBy(p => p.FirstName)
.Select(firstNameGroup => new
{
Name = firstNameGroup.Key,
Count = firstNameGroup.Count(),
});
foreach (var group in groups)
{
dtDataSource.Rows.Add(group.Name, group.Count);
}
LINQ Syntax
As a matter of personal preference, I typically use the LINQ extension methods (.Where, .Select, .GroupBy, etc.) instead of the query syntax. Following the style from your example above, the same query could be written as:
var groups = from p in people
where p.BirthDate >= startDate && p.BirthDate <= endDate
group p by p.FirstName into g
select new
{
Name = g.Key,
Count = g.Count(),
};

Entity Framework date combination query

I have a list of Apartments where each Apartment has it's ApartmentRooms, where each ApartmentRoom has it's DateBatches. Each DateBatch has list of Dates which represents occupied dates (one record per taken date).
Apartment 1
Room 1
DateBatch
1.5.2015
2.5.2015
DateBatch 2
8.5.2015
9.5.2015
Room 2
DateBatch
5.5.2015
6.5.2015
By this, you can see that this apartment has 2 rooms of which Room 1 is taken on following dates 1.5, 2.5, 8.5 and 9.5 and room 2 is occupied on 5.5 and 6.5.
User can enter a desired period of N days and X amount of consecutive days he wants to stay.
So for an example, user enters period from 1.5 to 15.5 and he wants to sleep 10 nights, I need to list all apartments where at least one of the apartment rooms is available for any of possible date combinations, which would be following in this case:
1.5-10.5
2.5-11.5
3.5-12.5
4.5-13.5
5.5-14.5
So far I have tried this, and it works only for first foreach iteration, because foreach concatenates query with AND criteria, not OR criteria, and I think this is a very bad approach to go.
public static IQueryable<Apartment> QueryByPeriod(this IQueryable<Apartment> apartments, DateTime PeriodStart, DateTime PeriodEnd, int StayDuration)
{
var possibleDateRanges = new List<List<DateTime>>();
//set all possible start dates for the desired period
for (var i = PeriodStart; i <= PeriodEnd.AddDays(-StayDuration); i = i.AddDays(1))
{
List<DateTime> dates = new List<DateTime>();
foreach(var date in i.DateRange(i.AddDays(StayDuration-1)))
{
dates.Add(date);
}
possibleDateRanges.Add(dates);
}
//filter by date range
//select apartment rooms where one of possible combinations is suitable for selected period
foreach (var possibleDates in possibleDateRanges)
{
apartments = apartments.Where(m => m.ApartmentRooms
.Any(g => g.OccupiedDatesBatches.Select(ob => ob.OccupiedDates).Any(od => od.Any(f => possibleDates.Contains(f.Date)))
) == false);
}
return apartments;
}
Any suggestions?
To combine multiple conditions with OR, you can use (for example) LinqKit library. Install it via nuget, add using LinqKit; and then:
public static IQueryable<Apartment> QueryByPeriod(this IQueryable<Apartment> apartments, DateTime PeriodStart, DateTime PeriodEnd, int StayDuration) {
var possibleDateRanges = new List<Tuple<DateTime, DateTime>>();
// list all ranges, so for your example that would be:
//1.5-10.5
//2.5-11.5
//3.5-12.5
//4.5-13.5
//5.5-14.5
var startDate = PeriodStart;
while (startDate.AddDays(StayDuration - 1) < PeriodEnd) {
possibleDateRanges.Add(new Tuple<DateTime, DateTime>(startDate, startDate.AddDays(StayDuration - 1)));
startDate = startDate.AddDays(1);
}
Expression<Func<Apartment, bool>> condition = null;
foreach (var range in possibleDateRanges) {
Expression<Func<Apartment, bool>> rangeCondition = m => m.ApartmentRooms
// find rooms where ALL occupied dates are outside target interval
.Any(g => g.OccupiedDatesBatches.SelectMany(ob => ob.OccupiedDates).All(f => f.Date < range.Item1 || f.Date > range.Item2)
);
// concatenate with OR if necessary
if (condition == null)
condition = rangeCondition;
else
condition = condition.Or(rangeCondition);
}
if (condition == null)
return apartments;
// note AsExpandable here
return apartments.AsExpandable().Where(condition);
}
Note that I also modified your logic. Of course, this logic is perfect candidate for unit-testing, and if you are working on a serious project - you should definely test it using in-memory EF provider (or mocking) for different conditions.
Here is a pure (does not require external packages) LINQ to Entities solution.
Start with determining the list of the possible start dates:
var startDates = Enumerable.Range(0, PeriodEnd.Subtract(PeriodStart).Days - StayDuration + 1)
.Select(offset => PeriodStart.AddDays(offset))
.ToList();
Then use the following query:
var availableApartments = apartments.Where(a => a.ApartmentRooms.Any(ar =>
startDates.Any(startDate => !ar.OccupiedDatesBatches.Any(odb =>
odb.OccupiedDates.Any(od =>
od.Date >= startDate && od.Date < DbFunctions.AddDays(startDate, StayDuration))))));
The benefit of this solution is that it can easily be extended. The above query returns available apartments, but does not provide information which room is available and when - something you might need to provide to the user. Using the above approach, you can get that information like this:
public class AvailableApartmentInfo
{
public Apartment Apartment { get; set; }
public Room Room { get; set; }
public DateTime StartDate { get; set; }
}
var availableApartmentInfo =
from a in apartments
from ar in a.ApartmentRooms
from startDate in startDates
where !ar.OccupiedDatesBatches.Any(odb =>
odb.OccupiedDates.Any(od =>
od.Date >= startDate && od.Date < DbFunctions.AddDays(startDate, StayDuration)))
select new AvailableApartmentInfo { Apartment = a, Room = ar, StartDate = startDate };

Categories