Comment arrondir le temps aux X minutes les plus proches?

160

Y at - il une fonction simple pour arrondir UP une DateTimeaux 15 minutes les plus proches?

Par exemple

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

2011-08-11 17:00 reste comme 2011-08-11 17:00

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

TimS
la source

Réponses:

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

Exemple:

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}
dtb
la source
13
Cette solution vient d'être intégrée à ma bibliothèque utilitaire en tant que méthode d'extension.
JYelton
1
Faites attention aux temps d'arrondi qui sont proches de l'extrême supérieur. Cela peut provoquer la levée d'une exception si les Ticks que vous calculez sont supérieurs à DateTime.MaxValue.Ticks. Soyez prudent et prenez le minimum de votre valeur calculée et DateTime.MaxValue.Ticks.
Paul Raff
4
Ne perdez-vous pas les informations de l'objet DateTime avec cette méthode? Comme le genre et le fuseau horaire, s'il y en a?
Evren Kuzucuoglu
11
@ user14 .. Le (+ d.Ticks - 1) s'assure qu'il sera arrondi si nécessaire. Les / et * sont arrondis. Exemple du tour 12 au 5 suivant: (12 + 5 - 1) = 16, 16/5 = 3 (car il s'agit d'un type de données entier), 3 * 5 = 15. tada :)
Diego Frehner
12
@dtb un petit ajout, sinon c'est probablement un peu bogué: vous devez garder le type datetime ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy
107

J'ai trouvé une solution qui n'implique pas de multiplier et de diviser les long nombres.

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

Usage:

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
redent84
la source
8
Je pensais avec certitude que ce serait plus rapide que d'utiliser la multiplication et la division, mais mes tests montrent que ce n'est pas le cas. Sur 10000000 itérations, la méthode du module a pris ~ 610 ms sur ma machine, tandis que la méthode mult / div a pris ~ 500 ms. Je suppose que les FPU font des préoccupations de l'ancien un non-problème. Voici mon code de test: pastie.org/8610460
viggity
1
Grande utilisation des extensions. Merci!
TravisWhidden le
1
@Alovchin Merci. J'ai mis à jour la réponse. J'ai créé cette ideone avec votre code pour montrer la différence: ideone.com/EVKFp5
redent84
1
C'est assez vieux, mais est le dernier %d.Ticksen RoundUpnécessaire? d.Ticks - (dt.Ticks % d.Ticks))sera nécessairement inférieur à d.Ticks, donc la réponse devrait être la même correcte?
Nate Diamond
1
Soulignons simplement que le module est un nécessite une opération de division sur le CPU. Mais je conviens qu'il est plus élégant que d'utiliser la propriété d'arrondi des divisions entières.
Alex
19

si vous avez besoin d'arrondir à un intervalle de temps le plus proche (pas supérieur), je suggère d'utiliser ce qui suit

    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);
    }
DevSal
la source
Cette réponse n'est pas arrondie correctement. user1978424 a le seul message qui montre correctement comment arrondir à l'intervalle le plus proche ci-dessous: (ironiquement voté à la baisse parce que la question était à peu
près arrondie à
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);
    }
}

Résultats:

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
Vlad Bezden
la source
3
2011-08-11 17:00:01obtient tronqué à2011-08-11 17:00:00
JYelton
1
@JYelton: Merci d'avoir indiqué +1. J'ai changé mon code pour tenir compte de cela.
Vlad Bezden
Fournir votre code au format Linqpad pour une vérification facile est un gain de temps considérable. Utilisation très simple.
Adam Garner
6

Puisque je déteste réinventer la roue, je suivrais probablement cet algorithme pour arrondir une valeur DateTime à un incrément de temps spécifié (Timespan):

  • Convertissez la DateTimevaleur à arrondir en une valeur décimale à virgule flottante représentant le nombre entier et fractionnaire d' TimeSpanunités.
  • Arrondissez cela à un entier, en utilisant Math.Round().
  • Remettez à l'échelle les graduations en multipliant l'entier arrondi par le nombre de graduations dans l' TimeSpanunité.
  • Instanciez une nouvelle DateTimevaleur à partir du nombre arrondi de graduations et renvoyez-la à l'appelant.

Voici le 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 ;
    }

}
Nicolas Carey
la source
Ce code est agréable pour arrondi au plus proche DateTime , mais je veux aussi la capacité à arrondir jusqu'à un multiple de unit . Passer MidpointRounding.AwayFromZeroà Roundn'a pas l'effet escompté. Avez-vous autre chose en tête en acceptant un MidpointRoundingargument?
HappyNomad
2

Ma version

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

En tant que méthode, il se verrouillerait comme ça

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

et s'appelle comme ça

DateTime thisIsNow = DateTime.Now;
DateTime nextQuarterHour = GetNextQuarterHour(thisIsNow);
soulflyman
la source
cela ne compte pas pour les secondes
Alex Norcliffe
1

Élégant?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)
Olaf
la source
1
Une version plus correcte serait: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), qui prend en charge la condition "reste".
Olaf
1

Attention: la formule ci-dessus est incorrecte, c'est à dire la suivante:

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

devrait être réécrit comme:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks/2) / d.Ticks) * d.Ticks);
}
user1978424
la source
1
Je ne suis pas d'accord. Puisque la division entière / d.Ticksarrondit à l'intervalle de 15 minutes le plus proche (appelons ces "blocs"), l'ajout d'un demi-bloc ne garantit pas l'arrondi vers le haut. Considérez quand vous avez 4,25 blocs. Si vous ajoutez 0,5 bloc, puis testez le nombre de blocs entiers que vous avez, vous n'en avez toujours que 4. Ajouter une coche de moins qu'un bloc complet est l'action correcte. Cela garantit que vous passez toujours à la plage de blocs suivante (avant d'arrondir vers le bas), mais vous empêche de vous déplacer entre les blocs exacts. (IE, si vous avez ajouté un bloc complet à 4,0 blocs, 5,0 arrondirait à 5, quand vous voulez 4. 4,99 sera 4.)
Brendan Moore
1

Une solution plus verbeuse, qui utilise modulo et évite les calculs inutiles.

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);
    }
}
Bo Sunesen
la source
0

Il s'agit d'une solution simple pour arrondir à la minute la plus proche. Il préserve les informations TimeZone et Kind de DateTime. Il peut être modifié en fonction de vos propres besoins (si vous devez arrondir aux 5 minutes les plus proches, etc.).

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;
dodgy_coder
la source
0

Vous pouvez utiliser cette méthode, elle utilise la date spécifiée pour garantir qu'elle conserve l'un des types de globalisation et de datetime précédemment spécifiés dans l'objet datetime.

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

Test de violon .Net

Si vous souhaitez utiliser TimeSpan pour arrondir, vous pouvez l'utiliser.

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

Du D.
la source
Que se passe-t-il si vous voulez arrondir à la 7e minute la plus proche var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// devrait être 9:42 mais aucune de ces méthodes ne fonctionne comme ça?
DotnetShadow le
Modifier semble que la réponse de @soulflyman produirait le bon résultat
DotnetShadow