如何按周分组日期?

31

我正在为一个自定义应用程序编写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)

有什么想法吗?

5个回答

53

这里的基本问题是如何将一个 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语句;分组逻辑不会改变。


1
我实际上添加了更多内容来考虑年份 //改编自https://dev59.com/HWoy5IYBdhLWcg3wa9ff Func<DateTime, int> weekProjector = d => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(d, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday) + (d.Year * 53); - mattylantz

44
尽管我不愿意与受人尊敬的回答者意见不同,但我认为这里接受的答案是错误的,并且这不是根本性地将日期投影到一年中的某一周的问题。
GetWeekOfYear()及其概念通常是根据某个约定标准来为一年中的周分配索引值。它不适用于将日期分组为七个相邻的天的群组,正如我所认为的问者要求的那样。
使用建议的 GetWeekOfYear() 不仅会导致许多年份在末尾拥有少于七天的组,而且更糟糕的是,由于 GetWeekOfYear() 支持的各种标准通常会将一年中的第一天分配给上一年的最后一周,然而 GetWeekOfYear() 的结果只包含整数周索引,没有参考相关年份,因此按以下方式分组: new { Year = date.Year, weekProjector(date) }date.Year + "-" + weekProjector(date) 使2011年1月1日与当年的圣诞节到新年前夕的12个月后分组。
因此,我认为原始问题基本上是一个将日期投影到“所有时间的一周”值的问题,你可以说“从 y/m/d 开始的那一周”,所以只需要按周的第一天进行分组,即(假设你默认为星期日):
group by date.AddDays(-(int)date.DayOfWeek)

1
我来到这个问题,是因为我已经有了非常类似于被接受答案的代码。我不喜欢它,原因和你所陈述的一样。这段代码更简单,而且对分段天数做得更好。谢谢!另外,你可以通过将那一天添加到这个结果中,使用不同的一周日期(如星期一)进行分段。 - user1304444
我修改了答案中的建议(顺便说一句,这是个很好的答案),将日期时间“捕捉”到最接近的时间段。按日期分组.AddDays(((int)date.DayOfWeek) < 4 ? (-(int)date.DayOfWeek) : (-(int)date.DayOfWeek + 7)) - g2server

8
除了Jon的回答,你还可以获取一周中第一天的日期,然后按该日期进行分组。
要获取一周中第一天的日期,您可以使用以下代码:
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);

0

我注意到原帖中的理想输出中有第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;
}

0
我试过这样做(而且它工作了 :))
@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>
        }
    }
}

如果您有更多的数据,可能会出现性能问题。 - Omar AMEZOUG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接