【问题标题】:How to group dates by week?如何按周分组日期?
【发布时间】:2012-01-23 14:38:16
【问题描述】:

我正在为我正在创建的定制应用程序编写 Excel 导出器,并且我对 C# 中的 LINQ 分组有疑问。

基本上,这个新的 Excel 导出器类有两个日期。然后该类检索此日期范围内的所有货物。

作为此导出器的一部分,我需要能够将日期分组为周,并获取该周的值。因此,例如,如果给我 07/12/2011 和 22/12/2011(dd/MM/yyyy 格式),我需要将它们之间的所有寄售范围分组为周(每周从星期日开始)。使用上述日期的理想结果是

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)

有什么想法吗?

【问题讨论】:

    标签: c# linq grouping


    【解决方案1】:

    这里的基本问题是如何将DateTime 实例投影到一年中的一周值。这可以通过调用Calendar.GetWeekOfYear 来完成。所以定义投影:

    Func<DateTime, int> weekProjector = 
        d => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
             d,
             CalendarWeekRule.FirstFourDayWeek,
             DayOfWeek.Sunday);
    

    您可以通过调整方法调用中的参数来准确配置“周数”的确定方式。您还可以决定将投影定义为例如如果您愿意,可以使用扩展方法;这不会改变代码的本质。在任何情况下,您都可以按周分组:

    var consignmentsByWeek = from con in consignments
                             group con by weekProjector(con.Date);
    

    如果您还想将输出限制为两个特定日期之间的货物,只需添加适当的where 子句;分组逻辑没有改变。

    【讨论】:

    【解决方案2】:

    犹豫不决,尽管我不同意作为尊敬的回答者,但我认为这里接受的答案是错误的,这从根本上不是一个预测一年中一周价值的问题。

    GetWeekOfYear() 和一般概念是关于根据一些商定的标准将索引值分配给一年内的周。正如我相信提问者所要求的那样,它不适合将日期分组为相邻的 7 天。

    使用 GetWeekOfYear() 不仅会导致在多年结束时少于 7 天的组,而且更糟糕的是,因为 GetWeekOfYear() 支持的各种标准通常会分配一年的头几天到上一年的最后一周,但 GetWeekOfYear() 结果仅包含整数周索引,没有参考相关年份,在提问者的年份按 new { Year = date.Year, weekProjector(date) }date.Year + "-" + weekProjector(date) 分组将看到 2011 年 1 月 1 日与圣诞节到新年前夜同年十二个月后

    所以我认为最初的问题是从根本上将 不是 投影到一年中的一周值,而是投影到 所有时间的一周 值,“一周开始 y /m/d" 你可能会说,所以分组只需要在一周的第一天完成,即(假设你很乐意默认为星期日):

    group by date.AddDays(-(int)date.DayOfWeek)
    

    【讨论】:

    • 我提出了这个问题,因为我已经有了看起来非常像公认答案的代码。我不喜欢它的原因和你说的一样。此代码更简单,并且可以更好地分割日子。谢谢!此外,您只需将这一天添加到此结果中,即可使用一周中的不同日期(如星期一)进行细分。
    • 我修改了答案中的建议(顺便说一句很好的答案),将日期时间“捕捉”到最近的时间段。 group by date.AddDays(((int)date.DayOfWeek) &lt; 4 ? (-(int)date.DayOfWeek) : (-(int)date.DayOfWeek + 7))
    【解决方案3】:

    除了 Jon's 回答之外,您还可以获取一周中第一天的日期,然后按该日期分组。

    获取一周中第一天的日期。 您可以使用此代码:

    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;
        }
    }
    

    然后你可以像这样按一周的第一个日期分组:

    var consignmentsByWeek = from con in consignments
                             group con by con.Datedate.StartOfWeek(DayOfWeek.Monday);
    

    【讨论】:

      【解决方案4】:

      我试过这样(它正在工作:))

      @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>
              }
          }
      }
      

      【讨论】:

      • 如果您有更多数据,性能问题
      【解决方案5】:

      我注意到 OP 在理想输出中有第 1 周、第 2 周等。这些不是一年中的一周,而是根据寄售日期显示的一周的“索引”。基于已经提供的其他一些答案,这是我的解决方案:

      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();
      }
      

      我们还需要定义这个类(或它的一个子集,具体取决于您要包含的数据):

      public class ConsignmentRange
      {
          public int EntryIndex;
          public int WeekOfYear;
          public bool FullWeek;
          public DateTime FirstDate;
          public DateTime LastDate;
          public DateTime RangeStart;
          public DateTime RangeEnd;
      }
      

      【讨论】:

        猜你喜欢
        • 2010-10-29
        • 2023-03-09
        • 2017-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-07
        • 1970-01-01
        相关资源
        最近更新 更多