簡體   English   中英

將日期范圍拆分為日期范圍塊

[英]Split date range into date range chunks

我正在尋找一種按天的塊大小將日期范圍拆分為一系列日期范圍的方法。 我計划使用它來緩沖對服務的調用,如果日期范圍太大,服務就會出錯。

這是我到目前為止想出的。 它似乎有效,但我不確定它是否會正確退出。 這似乎是以前可能已經做過幾次的事情,但我找不到它。

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

我正在尋找改進建議,或者“伙計,為此使用此現有功能!”

我認為當開始和結束之間的差異小於 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);
}

你的代碼對我來說很好。 我真的不喜歡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)));
    }
}

我沒有任何實現的對象。 它們實際上是相同的。

您的解決方案有幾個問題:

  • 測試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更清楚。

關於接受的答案,您可以使用元組的簡短形式:

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

如果您知道要將時間范圍分成多少個塊/間隔/周期/部分,我發現以下內容會有所幫助

您可以使用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轉換為您想要表示的時間范圍(元組、專用日期范圍對象等)。 你也可以修改這個函數,直接以你需要的形式返回。

到目前為止,答案中有很多未處理的極端情況。 並且不完全清楚您希望如何處理它們。 你想要重疊范圍的開始/結束嗎? 是否有最小范圍大小? 下面是一些可以處理一些極端情況的代碼,您必須特別考慮重疊,並且可能根據您返回的數據將范圍的開始/結束時間推遲幾秒鍾或更長時間。

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

在大多數情況下,公認的解決方案看起來不錯。 如果您需要消除每個塊的開頭和結尾的重疊,那么這可能會更好。

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

野兔是按月拼接的例子

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM