I am writing an Excel exporter for a bespoke application I am creating, and I have a question about LINQ grouping in C#.
Basically, this new Excel exporter class is given two dates. The class then retrieves all consignments between this date range.
As part of this exporter, I need to be able to group the dates into weeks, and get the values for that week. So for example, if I'm given 07/12/2011 and 22/12/2011 (dd/MM/yyyy format), I need to group all consignments between them ranges into weeks (each week beginning with Sunday). The ideal result using the above dates would be
Week 1: (consignments between 04/12/2011 and 10/12/2011)
Week 2: (consignments between 11/12/2011 and 17/12/2011)
Week 3: (consignments between 18/11/2011 and 24/12/2011)
Any ideas?
The fundamental question here is how to project a DateTime instance into a week of year value. This can be done using by calling Calendar.GetWeekOfYear. So define the projection:
Func<DateTime, int> weekProjector =
d => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
d,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Sunday);
You can configure exactly how the "week number" is determined by tweaking the parameters in the method call. You can also decide to define the projection as e.g. an extension method if you prefer; this does not change the essence of the code. In any case, you are then ready to group by week:
var consignmentsByWeek = from con in consignments
group con by weekProjector(con.Date);
If you also want to constrain the output to consigments between two specific dates, just add an appropriate where clause; the grouping logic does not change.
Hesitant though I am to disagree with as esteemed an answerer I believe the accepted answer here is wrong, and this is not fundamentally a question of projecting to a week of year value.
GetWeekOfYear(), and the concept in general, is about assigning index values to weeks within a year according to some agreed standard. It is not suitable for placing dates into groups of seven adjacent days as I believe the questioner requires.
Not only will use of GetWeekOfYear() as proposed result in groups of fewer than seven days at the end of many years, but worse still, as the various standards supported by GetWeekOfYear() will often apportion the first days of a year to the last week of the previous year, and yet the GetWeekOfYear() result contains only the integer week index with no reference to associated year, grouping by new { Year = date.Year, weekProjector(date) } or date.Year + "-" + weekProjector(date) in the questioner's year would see January 1st 2011 grouped in with Christmas Day through to New Year's Eve twelve months later that same year.
So I would argue that the original question is fundamentally one of projecting not to a week of year value but to a week of all time value, "week beginning y/m/d" you might say, so grouping need only be done by the first day of the week, i.e. (assuming you're happy to default to Sunday) simply:
group by date.AddDays(-(int)date.DayOfWeek)
In addition to Jon's answer you can get the date of the first day in the week then group by that date.
To get the date of the first day in the week.
you can use this code:
public static class DateTimeExtensions
{
public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek)
{
int diff = dt.DayOfWeek - startOfWeek;
if (diff < 0)
{
diff += 7;
}
return dt.AddDays(-1 * diff).Date;
}
}
then you can group by the first date of the week like this:
var consignmentsByWeek = from con in consignments
group con by con.Datedate.StartOfWeek(DayOfWeek.Monday);
I tried like this (and it's working :) )
#foreach (var years in _dateRange.GroupBy(y => y.Year))
{
<p>#years.Key</p>
foreach (var months in years.GroupBy(m => m.Month))
{
<p>#CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(months.Key)</p>
foreach (var weeks in months.GroupBy(w => w.AddDays(-(int)w.DayOfWeek)))
{
<p>#weeks.Key.ToString("dd-MMM-yy")</p>
}
}
}
I noticed that the OP has week 1, week 2, etc. in the ideal output. These are not the week of the year, but the "index" of the week being displayed based on the consignment dates. Building on some of the other answers already provided, here is my solution:
void DoExample()
{
//Load some sample data
var range = new List<DateTime>();
var curDate = DateTime.ParseExact("07/12/2011", "dd/MM/yyyy", System.Globalization.CultureInfo.InvariantCulture);
var maxDate = DateTime.ParseExact("22/12/2011", "dd/MM/yyyy", System.Globalization.CultureInfo.InvariantCulture);
while(curDate < maxDate)
{
range.Add(curDate);
curDate = curDate.AddDays(1);
}
//Run the method to get the consignments
var c = GetConsignments(range, DayOfWeek.Sunday);
//Output to match OP's "ideal" specs
foreach(var v in c)
{
Console.WriteLine($"Week {v.EntryIndex + 1} (number {v.WeekOfYear} in year): (consignments between {v.RangeStart:dd/MM/yyyy} and {v.RangeEnd:dd/MM/yyyy}). Actual date range is {v.RangeStart:dd/MM/yyyy}-{v.RangeEnd:dd/MM/yyyy} ({(v.FullWeek ? "Full" : "Partial")} week)");
}
//Most other answers place a lot of value on the week of the year, so this would include that.
// Also includes the actual date range contained in the set and whether all dates in that week are present
foreach (var v in c)
{
Console.WriteLine($"Week {v.EntryIndex + 1} (number {v.WeekOfYear} in year): (consignments between {v.RangeStart} and {v.RangeEnd})");
}
}
//Note that this lets us pass in what day of the week is the start.
// Not part of OP's requirements, but provides added flexibility
public List<ConsignmentRange> GetConsignments(IEnumerable<DateTime>consignments, DayOfWeek startOfWeek=DayOfWeek.Sunday)
{
return consignments
.OrderBy(v => v)
.GroupBy(v => v.AddDays(-(int)((7 - (int)startOfWeek) + (int)v.DayOfWeek) % 7))
.Select((v, idx) => new ConsignmentRange
{
//These are part of the OP's requirement
EntryIndex = idx,
RangeStart = v.Key, // part of requirement
RangeEnd = v.Key.AddDays(6), // part of requirement
//These are added as potentially useful
WeekOfYear = CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
v.Key, CalendarWeekRule.FirstFourDayWeek, startOfWeek),
FirstDate = v.Min(),
LastDate = v.Max(),
FullWeek = (v.Distinct().Count() == 7)
}
)
.ToList();
}
We'll also need this class defined (or a subset of it depending on what data you want to include):
public class ConsignmentRange
{
public int EntryIndex;
public int WeekOfYear;
public bool FullWeek;
public DateTime FirstDate;
public DateTime LastDate;
public DateTime RangeStart;
public DateTime RangeEnd;
}
Related
I am currently working with real-estate data, and each Listing entity has a ListingDate and a CloseDate. What I am currently trying to do is to count how many Listings are active in a given month and year (group by year and month).
So for an example if Listing1 had an ListingDate of 05/01/2020 and a CloseDate of 08/01/2020, there would be 1 Active count for May, June, July, and August and a year total of 4.
I am using EF and LINQ, and was wondering if I could solve it somehow.
Any help or advice is appreciated.
Sure you can; if you map listings to each month in which it's active, you can then simply group the results by month and get the counts trivially. Thus, the trickiest part is to just come up with the month DateTime values, which isn't that tricky.
Extension method to get month DateTimes from a start and end date:
public static IEnumerable<DateTime> GetMonths(this DateTime startDate, DateTime endDate)
{
var monthDiff = (endDate.Month - startDate.Month) + (12 * (endDate.Year - startDate.Year));
var startMonth = new DateTime(startDate.Year, startDate.Month, 1);
return Enumerable.Range(0, monthDiff + 1)
.Select(i => startMonth.AddMonths(i));
}
Create lookup:
var listingsByMonth = listings
.SelectMany(l =>
{
return l.ListingDate.GetMonths(l.ClosingDate.AddDays(-1)) // assuming closing date is exclusive
.Select(dt => new KeyValuePair<DateTime, Listing>(dt, l));
})
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
Demonstration of results:
foreach(var g in listingsByMonth)
{
Console.WriteLine($"{g.Key:yyyy-MM}: {g.Count()}");
}
Fiddle
Let's assume that date is given in DateTime structs. (You can parse text input to DateTime, check this) We can iterate over a List containing Listing entities, and perform a check to see if given date is in the range of ListingDate and ClosingDate. If the check succeeds, copy the entity to another list.
DateTime query = ...;
List<Listing> list = ...;
List<Listing> pass = new();
foreach (Listing entity in list)
{
if (entity.ListingTime < query && query < entity.ClosingTime)
pass.Add(entity)
}
While checking whether the query is in range, we could've used DateTime.Compare() but less than/greater than operators make the statement easier to read.
What would be the best choice in getting and returning a list of specific dates between a 3 week range?
My intent is to create delivery dates based on a delivery centre's given days they are available
public List<DayOfWeek> DeliveryDays { get; set; }
DeliveryDays contains set values from 0-6 (0 being Sunday, 1 Monday, etc.)
I want to get those values, pass them through 3 weeks worth of following dates, and return those delivery days in a list (so only those centre's can order on select days).
Here's what I have so far:
public List<DateTime> CalculateAvailableDeliveryDates()
{
//Loop through 3 weeks of days
DateTime today = DateTime.Today; //Specify today's date
DateTime totalDateCount = today.AddDays(1); //Plus one day on each time counted
var dates = Enumerable.Range(0, 21).Select(days => totalDateCount.AddDays(days)).ToList(); //Count from 0 to 21 (3 weeks worth of days). On each count, run totalDateCount
//if exists in deliveryDayList
//add to dates via dates.Add func
if (DeliveryDays.Contains(DayOfWeek.Monday))
{
//action
} //and so on for each day
//return specific dates from date range
return dates;
}
currently I get a readout of 21 days. The if statement does nothing and is only serving as an example of my logic.
Would the best method be: rather than getting a list first, to do a check and nest if/case statements based on the DeliveryDates per centre and then return them into a list?
Thanks in advance.
Given a list of DayOfWeek, you can select all dates in the next 21 days that match one of those days of the week using System.Linq. The Enumerable.Range selects a range of numbers, Select will then select a bunch of DateTime objects representing Today plus some number of days, and Where is used to filter the results, comparing the DayOfWeek for each date to see if it exists in DeliveryDays:
List<DayOfWeek> DeliveryDays = new List<DayOfWeek>();
public List<DateTime> GetAvailableDeliveryDates()
{
// 1. Get a range of numbers representing the days to add
// to today, which will make up our range of dates
// 2. Select a date using Today.AddDays for each number
// 3. Filter on only days which are contained in DeliveryDays
return Enumerable.Range(0, 21) // Define the range
.Select(i => DateTime.Today.AddDays(i)) // Select the range
.Where(date => DeliveryDays.Contains(date.DayOfWeek)) // Filter the range
.ToList();
}
Good day, I have a list, with some dates, I need to get The number of days of a day of the week, for example, get in a int variable the number of dates that are on Sunday, another int for Monday...
I've tried using the following code:
List<int> SundayDates = DateList.FindAll(x => x.DayOfWeek = DayOfWeek.Sunday)
but it says that it's read only, (And DateList, is the list where I storage my dates and where I want to get it).
You need to use ==, that is for equality checking. A single = is an assignment
To count the number of occurrences use Count.
int numberOfSundays = DateList.Count(x => x.DayOfWeek == DayOfWeek.Sunday);
Here is a count for each day of the week. The grouped join ensures that even if a day is not present in the list (example no occurrences of Monday) that it will still occur in the resulting list with a count of 0.
dotnetfiddle
var DateList = new List<DateTime>(); // your populated list of dates
var allDaysOfWeek = Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>();
var totalDayCounts = allDaysOfWeek.GroupJoin(DateList, dayOfWeek => dayOfWeek, date => date.DayOfWeek, (dayOfWeek, times) => new
{
DayOfTheWeek = dayOfWeek,
DayOfWeekCount = times.Count()
});
do not forget to add using System.Linq; at the top of your code file
I have a list of DateTime values and I want to split the whole list into sublists for each week.
The dates might span across multiple years(the user selects the start and end date), so a solution that splits them up per calendar week number would end up in conflicts.
Is there a way to traverse the list and then store each week's DateTime values in a new 2d list?
The values available are from Monday to Friday and first and last week might have fewer values.
The only relevant question I found is How to group dates by weeks but it is not suitable for my case.
You can use this method to get the week-number of a given DateTime. Then you can use Enumerable.GroupBy with an anonymous type containing the year and the weeknum:
var yearWeekGroups = allDates.GroupBy(d => new { d.Year, WeekNum = GetIso8601WeekOfYear(d) });
If you want a List<List<DateTime>> where each sub-list contains the dates of a week:
List<List<DateTime>> allWeeks = yearWeekGroups.Select(g => g.ToList()).ToList();
If your country doesn't use ISO 8601 you can use Calendar.GetWeekOfYear:
var cc = CultureInfo.CurrentCulture;
var yearWeekGroups = allDates.GroupBy(d => new
{
d.Year,
WeekNum = currentCulture.Calendar.GetWeekOfYear(d, cc.DateTimeFormat.CalendarWeekRule, cc.DateTimeFormat.FirstDayOfWeek)
});
I have problem in finding the date using day of the week.
For example : i have past date lets say,
Date date= Convert.TodateTime("01/08/2013");
08th Jan 2013 th Day of the week is Tuesday.
Now i want current week's tuesday's date. How i can do it.
Note : The past date is dynamic. It will change in every loop.
You can use the enumeration DayOfWeek
The DayOfWeek enumeration represents the day of the week in calendars
that have seven days per week. The value of the constants in this
enumeration ranges from DayOfWeek.Sunday to DayOfWeek.Saturday. If
cast to an integer, its value ranges from zero (which indicates
DayOfWeek.Sunday) to six (which indicates DayOfWeek.Saturday).
We can use the conversion to integer to calculate the difference from the current date of the same week day
DateTime dtOld = new DateTime(2013,1,8);
int num = (int)dtOld.DayOfWeek;
int num2 = (int)DateTime.Today.DayOfWeek;
DateTime result = DateTime.Today.AddDays(num - num2);
This also seems appropriate to create an extension method
public static class DateTimeExtensions
{
public static DateTime EquivalentWeekDay(this DateTime dtOld)
{
int num = (int)dtOld.DayOfWeek;
int num2 = (int)DateTime.Today.DayOfWeek;
return DateTime.Today.AddDays(num - num2);
}
}
and now you could call it with
DateTime weekDay = Convert.ToDateTime("01/08/2013").EquivalentWeekDay();
I may be a bit late to the party, but my solution is very similar:
DateTime.Today.AddDays(-(int)(DateTime.Today.DayOfWeek - DayOfWeek.Tuesday));
This will get the Tuesday of the current week, where finding Tuesday is the primary goal (I may have misunderstood the question).
You can use this....
public static void Main()
{
//current date
DateTime dt = DateTime.UtcNow.AddHours(6);
//you can use it custom date
var cmYear = new DateTime(dt.Year, dt.Month, dt.Day);
//here 2 is the day value of the week in a date
var customDateWeek = cmYear.AddDays(-2);
Console.WriteLine(dt);
Console.WriteLine(cmYear);
Console.WriteLine("Date: " + customDateWeek);
Console.WriteLine();
Console.ReadKey();
}