Wie kann ich die Zeit auf die nächsten X Minuten aufrunden?


160

Gibt es eine einfache Funktion zum Abrunden UP eine DateTimeauf die nächsten 15 Minuten?

Z.B

2011-08-11 16:59 wird 2011-08-11 17:00

2011-08-11 17:00 bleibt als 2011-08-11 17:00

2011-08-11 17:01 wird 2011-08-11 17:15

Antworten:


286
DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Kind);
}

Beispiel:

var dt1 = RoundUp(DateTime.Parse("2011-08-11 16:59"), TimeSpan.FromMinutes(15));
// dt1 == {11/08/2011 17:00:00}

var dt2 = RoundUp(DateTime.Parse("2011-08-11 17:00"), TimeSpan.FromMinutes(15));
// dt2 == {11/08/2011 17:00:00}

var dt3 = RoundUp(DateTime.Parse("2011-08-11 17:01"), TimeSpan.FromMinutes(15));
// dt3 == {11/08/2011 17:15:00}

13
Diese Lösung hat es gerade als Erweiterungsmethode in meine Utility-Bibliothek geschafft.
JYelton

1
Achten Sie auf Rundungszeiten, die nahe am oberen Extrem liegen. Dies kann dazu führen, dass eine Ausnahme ausgelöst wird, wenn die von Ihnen berechneten Ticks größer als DateTime.MaxValue.Ticks sind. Seien Sie sicher und nehmen Sie das Minimum Ihres berechneten Werts und von DateTime.MaxValue.Ticks.
Paul Raff

4
Verlieren Sie mit dieser Methode keine Informationen aus dem DateTime-Objekt? Wie die Art und die Zeitzone, wenn es eingestellt sind?
Evren Kuzucuoglu

11
@ user14 .. Das (+ d.Ticks - 1) stellt sicher, dass es bei Bedarf aufgerundet wird. Die / und * runden. Beispielrunde 12 bis zur nächsten 5: (12 + 5 - 1) = 16, 16/5 = 3 (weil es sich um einen ganzzahligen Datentyp handelt), 3 * 5 = 15. tada :)
Diego Frehner

12
@dtb eine kleine Ergänzung, sonst ist es wahrscheinlich ein wenig abgehört: Sie müssen die Datum / Uhrzeit Art behalten ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy

107

Es wurde eine Lösung gefunden, bei der Zahlen nicht multipliziert und geteilt long werden.

public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
    var modTicks = dt.Ticks % d.Ticks;
    var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
    return new DateTime(dt.Ticks + delta, dt.Kind);
}

public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta, dt.Kind);
}

public static DateTime RoundToNearest(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    bool roundUp = delta > d.Ticks / 2;
    var offset = roundUp ? d.Ticks : 0;

    return new DateTime(dt.Ticks + offset - delta, dt.Kind);
}

Verwendung:

var date = new DateTime(2010, 02, 05, 10, 35, 25, 450); // 2010/02/05 10:35:25
var roundedUp = date.RoundUp(TimeSpan.FromMinutes(15)); // 2010/02/05 10:45:00
var roundedDown = date.RoundDown(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00
var roundedToNearest = date.RoundToNearest(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00

8
Ich war mir sicher, dass dies schneller sein würde als Multiplikation und Division, aber meine Tests zeigen, dass dies nicht der Fall ist. Bei diesen über 10000000 Iterationen dauerte die Modulmethode auf meinem Computer ~ 610 ms, während die Mult / Div-Methode ~ 500 ms dauerte. Ich denke, FPUs machen die Sorgen der alten Zeit zu einem Nicht-Thema. Hier ist mein Testcode
Viggity

1
Gute Verwendung von Erweiterungen. Vielen Dank!
TravisWhidden

1
@Alovchin Danke. Ich habe die Antwort aktualisiert. Ich habe dieses Ideone mit Ihrem Code erstellt, um den Unterschied zu zeigen: ideone.com/EVKFp5
redent84

1
Das ist ziemlich alt, aber ist die letzte %d.Ticksin RoundUpnotwendig? d.Ticks - (dt.Ticks % d.Ticks))wird notwendigerweise kleiner sein als d.Ticks, also sollte die Antwort gleich richtig sein?
Nate Diamond

1
Nur um darauf hinzuweisen, Modul ist eine Teilungsoperation auf der CPU erforderlich. Aber ich stimme zu, dass es eleganter ist, die Rouding-Down-Eigenschaft von Integer-Divisionen zu verwenden.
Alex

19

Wenn Sie auf das nächste Zeitintervall runden müssen (nicht aufwärts), empfehle ich Folgendes

    static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
    {
        int f=0;
        double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
        if (m >= 0.5)
            f=1;            
        return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
    }

Diese Antwort rundet nicht richtig. user1978424 hat den einzigen Beitrag, der richtig zeigt, wie man auf das nächste Intervall unten rundet: (ironischerweise herabgestimmt, weil die Frage
ungefähr aufgerundet wurde

8
void Main()
{
    var date1 = new DateTime(2011, 8, 11, 16, 59, 00);
    date1.Round15().Dump();

    var date2 = new DateTime(2011, 8, 11, 17, 00, 02);
    date2.Round15().Dump();

    var date3 = new DateTime(2011, 8, 11, 17, 01, 23);
    date3.Round15().Dump();

    var date4 = new DateTime(2011, 8, 11, 17, 00, 00);
    date4.Round15().Dump();
}

public static class Extentions
{
    public static DateTime Round15(this DateTime value)
    {   
        var ticksIn15Mins = TimeSpan.FromMinutes(15).Ticks;

        return (value.Ticks % ticksIn15Mins == 0) ? value : new DateTime((value.Ticks / ticksIn15Mins + 1) * ticksIn15Mins);
    }
}

Ergebnisse:

8/11/2011 5:00:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:00:00 PM

3
2011-08-11 17:00:01wird abgeschnitten auf2011-08-11 17:00:00
JYelton

1
@JYelton: Danke, dass du auf +1 hingewiesen hast. Ich habe meinen Code geändert, um dem Rechnung zu tragen.
Vlad Bezden

Die Bereitstellung Ihres Code-Linqpad-Formats zur einfachen Überprüfung spart viel Zeit. Sehr einfach zu bedienen.
Adam Garner

6

Da ich es hasse, das Rad neu zu erfinden, würde ich wahrscheinlich diesem Algorithmus folgen, um einen DateTime-Wert auf ein bestimmtes Zeitinkrement (Zeitspanne) zu runden:

  • Konvertieren Sie den DateTimezu rundenden Wert in einen dezimalen Gleitkommawert, der die gesamte und gebrochene Anzahl von TimeSpanEinheiten darstellt.
  • Runden Sie das mit auf eine ganze Zahl Math.Round().
  • Skalieren Sie zurück zu Ticks, indem Sie die gerundete Ganzzahl mit der Anzahl der Ticks in der TimeSpanEinheit multiplizieren .
  • Instanziieren Sie einen neuen DateTimeWert aus der gerundeten Anzahl von Ticks und geben Sie ihn an den Anrufer zurück.

Hier ist der Code:

public static class DateTimeExtensions
{

    public static DateTime Round( this DateTime value , TimeSpan unit )
    {
        return Round( value , unit , default(MidpointRounding) ) ;
    }

    public static DateTime Round( this DateTime value , TimeSpan unit , MidpointRounding style )
    {
        if ( unit <= TimeSpan.Zero ) throw new ArgumentOutOfRangeException("unit" , "value must be positive") ;

        Decimal  units        = (decimal) value.Ticks / (decimal) unit.Ticks ;
        Decimal  roundedUnits = Math.Round( units , style ) ;
        long     roundedTicks = (long) roundedUnits * unit.Ticks ;
        DateTime instance     = new DateTime( roundedTicks ) ;

        return instance ;
    }

}

Das ist schön Code für an der Rundung am nächsten DateTime , aber ich mag auch die Möglichkeit, rund um bis zu einem Vielfachen unit . Das Weitergeben MidpointRounding.AwayFromZeroan Roundhat nicht den gewünschten Effekt. Haben Sie etwas anderes im Sinn, indem Sie ein MidpointRoundingArgument akzeptieren ?
HappyNomad

2

Meine Version

DateTime newDateTimeObject = oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);

Als Methode würde es so sperren

public static DateTime GetNextQuarterHour(DateTime oldDateTimeObject)
{
    return oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);
}

und heißt so

DateTime thisIsNow = DateTime.Now;
DateTime nextQuarterHour = GetNextQuarterHour(thisIsNow);

Dies macht keine Sekunden aus
Alex Norcliffe

1

Elegant?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)

1
Eine korrektere Version wäre: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), die sich darum kümmert die Bedingung "bleibt".
Olaf

1

Achtung: Die obige Formel ist falsch, dh die folgende:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks);
}

sollte umgeschrieben werden als:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks/2) / d.Ticks) * d.Ticks);
}

1
Ich bin nicht einverstanden. Da die Ganzzahldivision / d.Ticksauf das nächste 15-Minuten-Intervall abgerundet wird (nennen wir diese "Blöcke"), garantiert das Hinzufügen nur eines halben Blocks keine Aufrundung. Überlegen Sie, wann Sie 4,25 Blöcke haben. Wenn Sie 0,5 Blöcke hinzufügen, testen Sie, wie viele Ganzzahlblöcke Sie haben. Sie haben immer noch nur 4. Das Hinzufügen eines Ticks weniger als ein vollständiger Block ist die richtige Aktion. Es stellt sicher, dass Sie immer zum nächsten Blockbereich aufsteigen (bevor Sie abrunden), verhindert jedoch, dass Sie zwischen exakten Blöcken wechseln. (IE, wenn Sie einen vollständigen Block zu 4.0 Blöcken hinzugefügt haben, würde 5.0 auf 5 runden, wenn Sie 4 möchten. 4.99 wird 4. sein)
Brendan Moore

1

Eine ausführlichere Lösung, die Modulo verwendet und unnötige Berechnungen vermeidet.

public static class DateTimeExtensions
{
    public static DateTime RoundUp(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, true);
    }

    public static DateTime RoundDown(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, false);
    }

    private static DateTime Round(DateTime dt, TimeSpan ts, bool up)
    {
        var remainder = dt.Ticks % ts.Ticks;
        if (remainder == 0)
        {
            return dt;
        }

        long delta;
        if (up)
        {
            delta = ts.Ticks - remainder;
        }
        else
        {
            delta = -remainder;
        }

        return dt.AddTicks(delta);
    }
}

0

Dies ist eine einfache Lösung, um auf die nächste Minute aufzurunden. Die TimeZone- und Kind-Informationen der DateTime bleiben erhalten. Es kann weiter an Ihre eigenen Bedürfnisse angepasst werden (wenn Sie auf die nächsten 5 Minuten runden müssen usw.).

DateTime dbNowExact = DateTime.Now;
DateTime dbNowRound1 = (dbNowExact.Millisecond == 0 ? dbNowExact : dbNowExact.AddMilliseconds(1000 - dbNowExact.Millisecond));
DateTime dbNowRound2 = (dbNowRound1.Second == 0 ? dbNowRound1 : dbNowRound1.AddSeconds(60 - dbNowRound1.Second));
DateTime dbNow = dbNowRound2;

0

Sie können diese Methode verwenden. Sie verwendet das angegebene Datum, um sicherzustellen, dass die zuvor im Datum / Uhrzeit-Objekt angegebenen Globalisierungs- und Datums- / Uhrzeitarten beibehalten werden.

const long LNG_OneMinuteInTicks = 600000000;
/// <summary>
/// Round the datetime to the nearest minute
/// </summary>
/// <param name = "dateTime"></param>
/// <param name = "numberMinutes">The number minute use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, int numberMinutes = 1)
{
    long roundedMinutesInTicks = LNG_OneMinuteInTicks * numberMinutes;
    long remainderTicks = dateTime.Ticks % roundedMinutesInTicks;
    if (remainderTicks < roundedMinutesInTicks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundedMinutesInTicks - remainderTicks);
}

.Net Fiddle Test

Wenn Sie die Zeitspanne zum Runden verwenden möchten, können Sie diese verwenden.

/// <summary>
/// Round the datetime
/// </summary>
/// <example>Round(dt, TimeSpan.FromMinutes(5)); => round the time to the nearest 5 minutes.</example>
/// <param name = "dateTime"></param>
/// <param name = "roundBy">The time use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, TimeSpan roundBy)
{            
    long remainderTicks = dateTime.Ticks % roundBy.Ticks;
    if (remainderTicks < roundBy.Ticks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundBy.Ticks - remainderTicks);
}

TimeSpan Geige


Was passiert, wenn Sie auf die nächste 7. Minute runden möchten var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// sollte 9:42 sein, aber keine dieser Methoden funktioniert so?
DotnetShadow

Bearbeiten sieht aus wie @soulflyman Antwort würde das richtige Ergebnis produzieren
DotnetShadow
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.