将日期范围拆分为日期范围块

时间:2012-12-03 19:53:30

标签: c# .net

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

这是我到目前为止所提出的。它似乎工作,但我不确定它是否会正常退出。这看起来好像以前已经做了好几次,但我找不到它。

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

我正在寻找改进建议,或者“伙计,使用此现有功能!”

6 个答案:

答案 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}");
        }
    }