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

startとendの差が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これは、質問のコードで指定されているように、終了する最後の期間を切り捨てることに注意してください。それが必要ない場合は、の2行目を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

時間範囲を分割したいチャンク/間隔/期間/パーツの数がわかっている場合は、次のことが役立つことがわかりました。

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 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

受け入れられた解決策は、ほとんどの場合良さそうです。各チャンクの最初と最後のオーバーラップを取り除く必要がある場合は、これがうまくいく可能性があります。

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 に答える