25

我正在寻找一种按天的块大小将日期范围拆分为一系列日期范围的方法。我打算用它来缓冲对服务的调用,如果日期范围太大,服务就会出错。

到目前为止,这是我想出的。它似乎有效,但我不确定它是否会正确退出。这似乎以前可能已经做过好几次了,但我找不到。

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

我正在寻找改进建议,或者“老兄,为此使用这个现有功能!”

4

8 回答 8

47

我认为当开始和结束之间的差异小于 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);
}
于 2012-12-03T20:53:56.683 回答
2

你的代码对我来说看起来不错。我不太喜欢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)));
    }
}

我没有任何实现的对象。它们实际上是相同的。

于 2012-12-03T20:53:30.617 回答
2

您的解决方案存在几个问题:

  • 测试newEnd == end可能永远不会是真的,所以while可能永远循环(我现在看到这个条件应该总是被触发,但在第一次阅读代码时并不明显;while(true)感觉还是有点危险)
  • AddDays每次迭代调用 3 次(性能小问题)

这是一个替代方案:

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.

于 2012-12-03T20:53:44.613 回答
2

关于接受的答案,您可以使用元组的简短形式:

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);
}
于 2018-06-22T09:34:49.190 回答
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}");
        }
    }
于 2020-11-18T14:20:49.720 回答
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为您想要表示的时间范围(元组、专用日期范围对象等)。你也可以修改这个函数,直接以你需要的形式返回。

于 2018-08-14T13:19:32.177 回答
0

在大多数情况下,公认的解决方案看起来不错。如果您需要消除每个块的开头和结尾的重叠,那么这可能会更好。

public static IEnumerable<(DateTime FromDate, DateTime ToDate)> SplitDateRange(DateTime start, DateTime end, int dayChunkSize)
        {
            DateTime chunkEnd;
            while ((chunkEnd = start.AddDays(dayChunkSize-1)) < end)
            {
                yield return (start, chunkEnd);
                start = chunkEnd.AddDays(1);
            }
            yield return (start, end);
        }
于 2021-09-24T08:50:46.883 回答
0

野兔是一个按月拼接的例子

IEnumerable<(DateTime, DateTime)> SplitDateRange(DateTime start, DateTime end, int monthChunkSize)
{
    DateTime dateEnd=DateTime.Parse(end.ToString());
    
    for (int i = 0;start.AddMonths(i) < dateEnd; i+=monthChunkSize)
    {
            end = start.AddMonths(i+monthChunkSize);
             start.AddMonths(i);
        
        yield return (start.AddMonths(i), end<dateEnd?end:dateEnd);     
    }
}
于 2021-12-22T17:13:12.197 回答