从DateTime对象集合中解析每月项目

我之前已经问过这个问题 ,可以收集日期时间对象,并按天和时间对它们进行分组

所以回顾一下:我收集了DateTime的集合

List collectionOfDateTime = GetDateColletion(); 

然后通过dayofWeek和时间进行分组

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

所以在这一点上,我按周分组(一致时间)完美地工作。

我现在有一个新的要求按月而不是按周分组。 当我说“月”时,它不是一个月的同一天,而是像“每个月的第一个星期二”

我试图弄清楚在一个组中使用什么“关键”来对所有符合月度逻辑的项目进行分组(本月的第一个星期二,每个月的第二个星期五等)

举个例子,我可以说这些日期开始了;

 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 

如果这些是进入原始数组的日期,我需要一个groupby最终得到3组。

  • 第一组中包含date1和date2
  • 第二组将包含date3和date4。
  • date5将是独立的,因为它与给定不同时间的任何其他组不匹配

任何人都可以建议按照这个标准分组吗?

我觉得它比看起来容易:

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

局部变量h = (d.Day / 7) + 1设置该月内的DayOfWeek

我运行它进行测试并收到2组,与你的例子完全相同。 这些组的关键是:

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

什么意思,有“团体的第一个星期五 ”和“ 第一个星期六 ”的团体。

如果您愿意,可以通过d.Hour和/或d.Minute轻松扩展分组键:

 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; 

结果(仅限密钥):

 { 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月的第一个星期二”到“3月的第一个星期一”等组合在一起,这样你就可以获得这些“月”跨度,这些天数是可变的天数 – 取决于月份他们开始。 如果是这样,那么你真的需要使用一年中的日期将其分解为范围,这样:

 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. 

所以第一个范围是函数f ,使得f(32)= 1(DayOfYear是32),因为它落在2到37的范围内。这个f是一个索引的范围集合,在给定的DayOfYear集合中查找该项目落入,并将该项目的索引作为组号返回。

您可以通过从GetDateCollection获取最小和最大日期来动态构建此表,以确定整个范围。 因为围绕日期的逻辑本身就是一个相当复杂的话题,我会回到像NodaTime这样的库(特别是算术文档),从最小日期开始,一天一天地提前,直到我找到第一个资格日(即, “当月的第一个星期一”)并创建一个范围0到那一天 – 1作为组0并将其推送到索引集合(可能是ArrayList )。 然后使用LocalDate.PlusWeeks(1)从该日期开始循环,直到月份更改为止,构建新范围并将该范围推送到同一索引集合。

每次你进入新的一年,你必须在DayOfYear每年重置时建立你的索引集合时,向你的DayOfYear添加365(如果前一年是闰年则为366)。

现在,您已经获得了一系列范围,这些范围充当一个表,根据DayOfYear将天数分组为所需的单位。

编写一个遍历表的函数,将给定日期的DayOfYear( + [365|366] * x ,其中x是您比较的日期的年数)与集合中的项目进行比较,直到找到当天所在的范围,并将该项目的索引作为组号返回。 (或者,每个范围可以是Func ,如果提供的DateTime落在该范围内,则返回true。)

范围集合的另一种数据结构是一个ushort数组,其长度等于日期范围内从最小日期到最大日期的所有天数,以及每天它们分配的组编号的值(按范围计算,如上所述) 。 这将对分组执行得更快,但如果您使用较小的数据集(只有几百个日期),性能可能不会明显。

要使用Linq分组,也许这段代码可以帮助您:

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