Parse out monthly items from a collection of DateTime objects

蓝咒 提交于 2019-12-23 12:56:26

问题


I previously asked this question to take a oollection of datetime objects and group them by dayOfweek and time

So just to recap: I take a collection of DateTime

List<DateTime> collectionOfDateTime = GetDateColletion();

and then grouping by dayofWeek and time of day by doing this

var byDayOfWeek = collectionOfDateTime.GroupBy(dt => dt.DayOfWeek + "-" + dt.Hour + "-" + dt.Minute);

So at this point, I have these grouped by week (consistent time) working perfectly.

I now have a new requirement to group by Month instead of by week. When i say "month", its not the same day of the month but something like "the first tuesday of each Month"

I am trying to figure out what "key" to use in a group by to group all items that fit that monthly logic (the first tuesday of the month, the second friday of each month, etc)

As an example lets say i had these dates to start out with;

var date1 = new DateTime(2013, 1, 4).AddHours(8);  // This is the first Friday in Jan
var date2 = new DateTime(2013, 2, 1).AddHours(8);  // This is the first Friday in Feb
var date3 = new DateTime(2013, 1, 5).AddHours(3);  // This is the first Sat in Jan
var date4 = new DateTime(2013, 2, 2).AddHours(3);  // This is the first Sat in Feb
var date5 = new DateTime(2013, 2, 2).AddHours(6);  // This is the first Sat in Feb - different time

If these were the dates that went into the original array, i need a groupby to end up with 3 groups.

  • The first group would have date1 & date2 in it
  • The second group would have date3 and date4 in it.
  • date5 would be on its own as it doesn't match any of the other groups given the different time

Can anyone suggest anyway to group by that criteria?


回答1:


I think it's easier than it looks:

var byDayOfMonth = from d in dates
                   let h = (d.Day / 7) + 1
                   group d by new { d.DayOfWeek, h } into g
                   select g;

Local variable h = (d.Day / 7) + 1 sets which DayOfWeek within that month it actually is.

I run it for test and received 2 groups, exactly the same as in your example. Keys for that groups are:

{ DayOfWeek = Friday, h = 1 }
{ DayOfWeek = Saturday, h = 1 }

What means, there are groups for 'First Friday of month' and 'First Saturday of month'.

You can easily extend grouping key by d.Hour and/or d.Minute if you like:

var byDayOfMonth = from d in dates
                   let h = (d.Day / 7) + 1
                   group d by new { d.DayOfWeek, h, d.Hour, d.Minute } into g
                   select g;

Results (keys only):

{ DayOfWeek = Friday, h = 1, Hour = 8, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 3, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 6, Minute = 0 }



回答2:


There is probably an easier way to do this but this is what's come to me:

I gather from your question that you need to group everything from "the first Tuesday of February until the first Monday of March" etc. such that you get these "month" spans that are a variable number of days - depending on the month in which they start. If so then you really need to break this down into ranges using the day of the year so:

Group by the First Wednesday of the Month 2013
Group 0 (0-1) 
All DayOfYear between 0 and 1 2013
Group 1 (2-36) 
The first Wednesday of the month: January is DayOfYear 2. 
The first Wednesday of the month: February is DayOfYear 37. 
etc.

So the first range is a function f such that f(32) = 1 (DayOfYear is 32) because it falls in the range 2 to 37. This f is an indexed collection of ranges, finding the item in the collection that a given DayOfYear falls into, and returning that item's index as the group number.

You can dynamically build this table by getting your min and max dates from GetDateCollection to determine the overall range. Because the logic surrounding dates is a pretty complex topic in of itself I'd fall back on a library like NodaTime (specifically the arithmetic documentation), start with the min date, advance day by day until I found the first qualifying day (i.e., "first Monday of the month") and create a range 0 to that day - 1 as group 0 and push that onto an indexed collection (ArrayList likely). Then loop from that date using LocalDate.PlusWeeks(1) until the month changes, constructing a new range and pushing that range onto the same indexed collection.

Each time you cross into a new year you'll have to add 365 (or 366 if the previous year is a leap year) to your DayOfYear as you build your indexed collection since DayOfYear resets each year.

Now you've got a collection of ranges that acts as a table that groups days into the desired units based on their DayOfYear.

Write a function that traverses the table comparing the DayOfYear (+ [365|366] * x where x is the # of years the date you are comparing is from your min year) of a given date against the items in the collection until you locate the range that day falls within, and return that index of that item as the group number. (Alternatively each range could be a Func<DateTime,bool> that returns true if the provided DateTime falls in that range.)

An alternative data structure to the collection of ranges would be an array of ushort with length equal to all the days from min to max dates in your date range, and the value for each day their assigned group number (calculated with ranges, as above). This will perform faster for grouping, though the performance may not be noticeable if you're working with a smaller dataset (only a few hundred dates).




回答3:


To group by using Linq maybe this code will help you:

List<DateTime> collectionOfDateTime = GetDateColletion();
collectionOfDateTime.GroupBy(s => Convert.ToInt16(s.DayOfWeek) & s.Hour & s.Minute);


来源:https://stackoverflow.com/questions/15170391/parse-out-monthly-items-from-a-collection-of-datetime-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!