C# 如何按周分组日期?
我正在为我正在创建的定制应用程序编写一个Excel导出器,我对C#中的LINQ分组有一个问题 基本上,这个新的Excel exporter类有两个日期。然后,该类检索此日期范围内的所有委托 作为导出程序的一部分,我需要能够将日期分组为周,并获取该周的值。因此,例如,如果给我2011年12月7日和2011年12月22日(dd/MM/yyyy格式),我需要将它们之间的所有托运货物分组为周(每周从周日开始)。使用上述日期的理想结果是C# 如何按周分组日期?,c#,linq,grouping,C#,Linq,Grouping,我正在为我正在创建的定制应用程序编写一个Excel导出器,我对C#中的LINQ分组有一个问题 基本上,这个新的Excel exporter类有两个日期。然后,该类检索此日期范围内的所有委托 作为导出程序的一部分,我需要能够将日期分组为周,并获取该周的值。因此,例如,如果给我2011年12月7日和2011年12月22日(dd/MM/yyyy格式),我需要将它们之间的所有托运货物分组为周(每周从周日开始)。使用上述日期的理想结果是 Week 1: (consignments between 04/1
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)
有什么想法吗?这里的基本问题是如何将一个
DateTime
实例投影到一周的年值中。这可以通过调用来完成。因此,定义投影:
Func<DateTime, int> weekProjector =
d => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(
d,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Sunday);
如果您还想将输出限制为两个特定日期之间的一致性,只需添加一个适当的
where
子句;分组逻辑没有改变。虽然我不同意尊敬的回答者的观点,但我认为这里接受的答案是错误的,这从根本上讲不是预测一周的年值的问题
GetWeekOfYear()的概念通常是根据约定的标准为一年中的周分配索引值。我相信发问者所要求的那样,把日期分成七天一组是不合适的
不仅建议使用GetWeekOfYear()会导致在许多年结束时少于七天的组,更糟糕的是,GetWeekOfYear()支持的各种标准通常会将一年的第一天分配到上一年的最后一周,而GetWeekOfYear()则会结果仅包含整数周索引,不引用相关年份,按new{year=date.year,weekproject(date)}
或date.year+“-”+weekproject(date)分组
在提问者的一年中,2011年1月1日与圣诞节一起分组,直到同年12个月后的除夕
因此,我认为最初的问题基本上不是预测一周的年值,而是预测一周的所有时间值,你可能会说,“周开始y/m/d”,因此分组只需要在一周的第一天完成,即(假设你乐意默认为周日),简单地说:
除了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;
}
}
我试着这样做(而且成功了:)
@foreach(变量年在_dateRange.GroupBy(y=>y.Year))中)
{
@几年,钥匙
foreach(年内的var月数。GroupBy(m=>m.Month))
{
@CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(months.Key)
foreach(月内的var周数。GroupBy(w=>w.AddDays(-(int)w.DayOfWeek)))
{
@周。键。ToString(“dd-MMM-yy”)
}
}
}
我注意到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();
}
我提出这个问题,因为我已经有了与公认答案非常相似的代码。我不喜欢它的原因和你说的完全一样。这段代码更简单,在划分日期方面做得更好。谢谢此外,您可以使用一周中不同的一天(如周一)进行分段,只需将该天添加到此结果中即可。我修改了答案中的建议(顺便说一句,很棒的答案),将日期时间“捕捉”到最近的时段<代码>按日期分组.AddDays(((int)date.DayOfWeek)<4?((int)date.DayOfWeek):((int)date.DayOfWeek+7))如果您有更多数据,请执行问题
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);
@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>
}
}
}
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;
}