我正在寻找一种方法,将日期范围按日块大小分成一系列日期范围。我打算用它来缓冲对服务的调用,如果日期范围太大,服务就会出错。
这是我到目前为止所提出的。它似乎工作,但我不确定它是否会正常退出。这看起来好像以前已经做了好几次,但我找不到它。
public IEnumerable<Tuple<DateTime, DateTime>> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
{
var newStart = start;
var newEnd = start.AddDays(dayChunkSize);
while (true)
{
yield return new Tuple<DateTime, DateTime>(newStart, newEnd);
if (newEnd == end)
yield break;
newStart = newStart.AddDays(dayChunkSize);
newEnd = (newEnd.AddDays(dayChunkSize) > end ? end : newEnd.AddDays(dayChunkSize));
}
}
我正在寻找改进建议,或者“伙计,使用此现有功能!”
答案 0 :(得分:27)
我认为当开始和结束之间的差异小于dayChunkSize时,您的代码会失败。 见:
var singleRange = SplitDateRange(DateTime.Now, DateTime.Now.AddDays(7), dayChunkSize: 15).ToList();
Debug.Assert(singleRange.Count == 1);
建议的解决方案:
public static IEnumerable<Tuple<DateTime, DateTime>> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
{
DateTime chunkEnd;
while ((chunkEnd = start.AddDays(dayChunkSize)) < end)
{
yield return Tuple.Create(start, chunkEnd);
start = chunkEnd;
}
yield return Tuple.Create(start, end);
}
答案 1 :(得分:2)
您的解决方案存在一些问题:
newEnd == end
可能永远不会成立,所以while
可以永远循环while(true)
感觉还有点危险)AddDays
被调用三次这是另一种选择:
public IEnumerable<Tuple<DateTime, DateTime>> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
{
DateTime startOfThisPeriod = start;
while (startOfThisPeriod < end)
{
DateTime endOfThisPeriod = startOfThisPeriod.AddDays(dayChunkSize);
endOfThisPeriod = endOfThisPeriod < end ? endOfThisPeriod : end;
yield return Tuple.Create(startOfThisPeriod, endOfThisPeriod);
startOfThisPeriod = endOfThisPeriod;
}
}
请注意,这将截断end
上的最后一个句点,如问题代码中所示。如果不需要,可以省略while
的第二行,简化方法。此外,startOfThisPeriod
并非绝对必要,但我认为这比重用start
更清晰。
答案 2 :(得分:1)
您的代码对我来说很合适。我真的不喜欢while(true)
的想法
但其他解决方案是使用enumerable.Range:
public static IEnumerable<Tuple<DateTime, DateTime>> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
{
return Enumerable
.Range(0, (Convert.ToInt32((end - start).TotalDays) / dayChunkSize +1))
.Select(x => Tuple.Create(start.AddDays(dayChunkSize * (x)), start.AddDays(dayChunkSize * (x + 1)) > end
? end : start.AddDays(dayChunkSize * (x + 1))));
}
或者,这也有效:
public static IEnumerable<Tuple<DateTime, DateTime>> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
{
var dateCount = (end - start).TotalDays / 5;
for (int i = 0; i < dateCount; i++)
{
yield return Tuple.Create(start.AddDays(dayChunkSize * i)
, start.AddDays(dayChunkSize * (i + 1)) > end
? end : start.AddDays(dayChunkSize * (i + 1)));
}
}
我没有任何实现的任何对象。它们实际上完全相同。
答案 3 :(得分:1)
关于可接受的答案,您可以使用元组的缩写形式:
private static IEnumerable<(DateTime, DateTime)> GetDateRange1(DateTime startDate, DateTime endDate, int daysChunkSize)
{
DateTime markerDate;
while ((markerDate = startDate.AddDays(daysChunkSize)) < endDate)
{
yield return (startDate, markerDate);
startDate = markerDate;
}
yield return (startDate, endDate);
}
但是我更喜欢使用命名元组:
private static IEnumerable<(DateTime StartDate, DateTime EndDate)> GetDateRange(DateTime startDate, DateTime endDate, int daysChunkSize)
{
DateTime markerDate;
while ((markerDate = startDate.AddDays(daysChunkSize)) < endDate)
{
yield return (StartDate: startDate, EndDate: markerDate);
startDate = markerDate;
}
yield return (StartDate: startDate, EndDate: endDate);
}
答案 4 :(得分:0)
如果您知道要将时间范围划分为多少个块/间隔/期间/部分,我发现以下内容会有所帮助
您可以使用DateTime.Ticks属性定义间隔,然后根据定义的间隔创建一系列DateTime对象:
IEnumerable<DateTime> DivideTimeRangeIntoIntervals(DateTime startTS, DateTime endTS, int numberOfIntervals)
{
long startTSInTicks = startTS.Ticks;
long endTsInTicks = endTS.Ticks;
long tickSpan = endTS.Ticks - startTS.Ticks;
long tickInterval = tickSpan / numberOfIntervals;
List<DateTime> listOfDates = new List<DateTime>();
for (long i = startTSInTicks; i <= endTsInTicks; i += tickInterval)
{
listOfDates.Add(new DateTime(i));
}
return listOfDates;
}
您可以将listOfDates
转换为想要表示时间范围(元组,专用日期范围对象等)的格式。您还可以修改此函数以直接以您需要的形式返回它。
答案 5 :(得分:0)
到目前为止,答案中还有很多未解决的极端情况。尚不清楚您将如何处理它们。您要重叠范围的开始/结束吗?是否有最小范围大小?下面是一些处理某些极端情况的代码,您必须特别考虑重叠部分,并可能根据返回的数据将范围的开始/结束时间缩短几秒钟甚至更多。>
public static IEnumerable<(DateTime start, DateTime end)> PartitionDateRange(DateTime start,
DateTime end,
int chunkSizeInDays)
{
if (start > end)
yield break;
if (end - start < TimeSpan.FromDays(chunkSizeInDays))
{
yield return (start, end);
yield break;
}
DateTime e = start.AddDays(chunkSizeInDays);
for (;e < end; e = e.AddDays(chunkSizeInDays))
{
yield return (e.AddDays(-chunkSizeInDays), e);
}
if (e < end && end - e > TimeSpan.FromMinutes(1))
yield return (e, end);
}
示例调用:
static void Main(string[] _)
{
Console.WriteLine("expected");
DateTime start = DateTime.Now - TimeSpan.FromDays(10);
DateTime end = DateTime.Now;
foreach (var range in PartitionDateRange(start, end, 2))
{
Console.WriteLine($"{range.start} to {range.end}");
}
Console.WriteLine("start > end");
start = end + TimeSpan.FromDays(1);
foreach (var range in PartitionDateRange(start, end, 2))
{
Console.WriteLine($"{range.start} to {range.end}");
}
Console.WriteLine("less than partition size");
start = end - TimeSpan.FromDays(1);
foreach (var range in PartitionDateRange(start, end, 2))
{
Console.WriteLine($"{range.start} to {range.end}");
}
}