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.
Related
I previously asked this question here: List of Dates ordered in a certain way
I thought the proposed solution was fine until the year ticked over on my date list and encountered an issue.
My date list (in this string based format - this is how the data comes to me from the source API)
201711
201712
201801
201811
201812
201901
I want to present my data in a bar chart to show 3 months worth of year on year comparison in month order. This would mean I order the list as so
201711
201811
201712
201812
201801
201901
So I can then see year-on-year bars for November, December and Jan in that order.
I've tried the solution at the bottom of the question but it places the order like so (which is not what I want):
201801
201901
201711
201811
201712
201812
For clarity, next month it will need to move forward to be this date list:
the first month I want will always be 2 months before the current one
201712
201812
201801
201901
201802
201902
var rowsInOrder = dateList.OrderBy(n => DateTime.ParseExact(n, "yyyyMM", CultureInfo.CurrentCulture).Month).ThenBy(n=> DateTime.ParseExact(n, "yyyyMM", CultureInfo.CurrentCulture).Year);
You can use this Lookup approach that first determines the month-groups:
var monthLookup = dateList
.Select(s => new{String = s, Date = DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture)})
.OrderBy(x => x.Date) // not necessary in your sample data but i assume it's desired
.ToLookup(x=> x.Date.Month);
var rowsInOrder = monthLookup.SelectMany(x => x).Select(x => x.String);
I could manage to achieve your goal using GroupBy to group months,
var rowsInOrder = new List<string>();
foreach (var grouping in dates.GroupBy(s =>
DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month))
{
rowsInOrder.AddRange(grouping.OrderBy(s => s));
};
You can also order months with same logic:
var rowsInOrder = new List<string>();
foreach (var grouping in dates
.OrderBy(s => DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month).GroupBy(s =>
DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month))
{
rowsInOrder.AddRange(grouping.OrderBy(s => s));
}
It seems to me that this is sufficient:
var rowsInOrder = dates.OrderBy(x => x).GroupBy(x => x.Substring(4)).SelectMany(x => x);
There's simply no need to muck around with parsing dates. It's a simple string sort and group this way.
So you are stuck with a sequence of objects, where every object has a Date property of type string in the format yyyyMM. and you want to extract some data from it.
Your date format is language independent. It doesn't matter whether your computer is a British one, or a Chinese one. The Date property will always be in format yyyyMM.
This makes it fairly easy to convert it into a DateTime format, which makes it easy to access the Year and Month.
const string dateTimeFormat = "yyyyMM";
CultureInfo provider = CultureInfo.InvariantCulture;
var dateList = ... // your original list of items with the string Date property
var itemsWithYearMonth = dateList.Select(item => new
{
DateTime = DateTime.ParseExact(item, dateTimeFormat, provider)
... // select other items you need for your bar chart
});
Now, given a StartYear/Month, a NrOfYears and a NrOfMonths you want to group dateTimeItems into groups of same month.
For example, starting at 2018-11, I want groups with four months, for three consecutive years (yeah, yeah, I know in your original request it was only 3 months, 2 years, but why limit yourself to this, let's make your code re-usable):
group 1: 2018-11, 2019-11, 2020-11, 2021-11
group 2: 2018-12, 2019-12, 2020-12, 2021-12
group 3: 2019-01, 2020-01, 2021-01, 2022-01
Bonus-points: we'll pass the year boundary!
So input:
var itemsWithYearMonth = ... // see above
int startYear = ...
int startMonth = ...
int nrOfMonths = ...
int nrOfYears = ...
We will make groups of items with same months. We don't want all Months of the year, we only want some months. If we want the 3 months starting at month 11, we need to keep the groups with Months 11, 12, 1.
var desiredMonths = Enumerable.Range(startMonth, nrOfMonths) // example: 11, 12, 13
.Select(monthNr => 1 + ((monthNr-1) % 12)); // 11, 12, 1
From your input, we don't want all months, we only want the year/month larger than the starting month.
DateTime startMonth = new DateTime(startYear, startMonth, 1);
The easiest way is to keep only the input source of items with date equal or larger than startMonth and take only the first NumberOfYears items of each group. This way you get the correct number of items if you pass the year boundary like I did in my example.
var result = itemsWithYearMonth
// keep only the items newer than startMonth
.Where(item => item.DateTime >= startMonth)
// group by same month:
.GroupBy(item => item.DateTime.Month,
(month, itemsWithThisMonth) => new
{
Month = month, // in my example: 11, 12, 1, ...
// in every group: take the first nrOfYears items:
Items = itemsWithThisMonth
// order by ascending year
.OrderBy(itemWithThisMonth => itemWithThisMonth.Year)
// no need to order by Month, all Months are equal in this group
.Take(nrOfYears)
.ToList(),
})
// keep only the desired months:
.Select(group => desiredMonth.Contains(group.Month));
So now you have groups:
group of month 11, with data of years 2018, 2019, 2020, 2021
group of month 12, with data of years 2018, 2019, 2020, 2021
group of month 01, with data of years 2019, 2020, 2021, 2022
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
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Linq orderby, start with specific number, then return to lowest
I need to create a ComboBox that lists the months of the year, but need it to start with the
current month, and then the rest in month order, e.g.:
October
November
December
January
February
March
etc.....
The data source is a list of months in a database, which are numbered according to month number (i.e. January = 1, etc.) and then manipulated to give a datetime
How can I sort this list in C# so that I get the order I want?
TIA.
string[] months = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
var ordered = months.Skip(DateTime.Today.Month - 1)
.Concat(months.Take(DateTime.Today.Month - 1))
.Where(s=>!String.IsNullOrEmpty(s))
.ToList();
Use DateTimeFormatInfo.GetMonthName methoed
List<string> list = new List<string>();
DateTimeFormatInfo dtFI = new DateTimeFormatInfo();
DateTime currentDate = DateTime.Now;
DateTime nextyearDate = currentDate.AddYears(1).AddDays(-1);
while (currentDate < nextyearDate)
{
list.Add(dtFI.GetMonthName(currentDate.Month));
currentDate = currentDate.AddMonths(1);
}
This will create a new list of months, starting from current month.
Another take on this with LINQ:
// month name source, use what you prefer
var monthNames = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
var sorted = Enumerable.Range(1, 12).Zip(monthNames, Tuple.Create)
.OrderBy(t => (t.Item1 - DateTime.Today.Month + 12) % 12)
.Select(t => t.Item2)
.ToArray();
You can get the lists of months in C# with List<DateTime> monthNames = DateTimeFormatInfo.CurrentInfo.MonthNames.ToList();. It get a List<DateTime> with 13 elements (a null month name at the end, but you can always remove the last element monthNames.RemoveAt(monthNames.Count - 1);
http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.monthnames.aspx
To reorder this list you can get the current month number index using DateTime.Now.Month.ToString("00");, and restructure the list newMonthNames = monthNames.GetRange(index, 12 - index).AddRange(monthNames.GetRange(0, index);
There are many ways to do it, like others have shown.
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;
}
I'm working with a collection of DateTime with all dates from Date A to Date B.
I will be handed a string which looks like 1234567, 1 is sunday, 2 is tuesday, etc.
Now, imagine I want to filter my dates collection using a string with the above configuration and we get the string 1004007, meaning we will have to filter our DateTime collection to only have dates which occur on a sunday, on a wednesday and a saturday.
How can I read the whole string, figure out which days I will be filtering from and then dynamically filter my collection according to those days of the week?
Give this a shot:
List<DateTime> dates = ...;
string filter = "1004007";
List<DateTime> filteredDates = dates.Where(d =>
filter.Contains(((int)d.DayOfWeek + 1).ToString())).ToList();
Or, if you like, you can first construct a list of days that are your filter rather than just using the String.Contains function. If your list of dates is very large, doing this work up front could help performance:
List<DateTime> dates = ...;
string filter = "1004007";
var daysOfWeek = filter.Distinct().Where(c => c != '0')
.Select(c => (DayOfWeek)(int.Parse(c.ToString()) - 1))
List<DateTime> filteredDates = (from d in dates
join dw in daysOfWeek on d.DayOfWeek equals dw
select d).ToList();
Convert the day of the week to an integer, then a string, and use Contains to see if it is in your input string:
string days = "1004007";
var result = datetimes
.Where(dt => days.Contains(((int)dt.DayOfWeek + 1).ToString()));