Question

Pouvez-vous arrondir un objet .NET TimeSpan ?

J'ai une valeur Timespan de: 00: 00: 00.6193789

Existe-t-il un moyen simple de conserver un objet TimeSpan , mais de l’arrondir à quelque chose comme:
00: 00: 00.62?

Était-ce utile?

La solution

Désolé, les gars, mais les deux la question et la réponse populaire jusqu’à présent sont fausses: -)

La question est fausse, car Tyndall demande une solution pour arrondir , mais présente un exemple de troncature .

La réponse de Will Dean est fausse, car elle concerne également la troncature plutôt que l'arrondissement . (Je suppose que l’on pourrait dire que la réponse est bonne pour l’une des deux questions, mais laissons de côté la philosophie pour le moment ...)

Voici une technique simple pour arrondir :

int precision = 2; // Specify how many digits past the decimal point
TimeSpan t1 = new TimeSpan(19365678); // sample input value

const int TIMESPAN_SIZE = 7; // it always has seven digits
// convert the digitsToShow into a rounding/truncating mask
int factor = (int)Math.Pow(10,(TIMESPAN_SIZE - precision));

Console.WriteLine("Input: " + t1);
TimeSpan truncatedTimeSpan = new TimeSpan(t1.Ticks - (t1.Ticks % factor));
Console.WriteLine("Truncated: " + truncatedTimeSpan);
TimeSpan roundedTimeSpan =
    new TimeSpan(((long)Math.Round((1.0*t1.Ticks/factor))*factor));
Console.WriteLine("Rounded: " + roundedTimeSpan);

Avec la valeur d'entrée et le nombre de chiffres dans l'exemple de code, voici le résultat:

Input: 00:00:01.9365678
Truncated: 00:00:01.9300000
Rounded: 00:00:01.9400000

Modifiez la précision de 2 à 5 chiffres et obtenez-la à la place:

Input: 00:00:01.9365678
Truncated: 00:00:01.9365600
Rounded: 00:00:01.9365700

Et même le changer à 0 pour obtenir ce résultat:

Input: 00:00:01.9365678
Truncated: 00:00:01
Rounded: 00:00:02

Enfin, si vous voulez juste un peu plus de contrôle sur la sortie, ajoutez un peu de formatage. Voici un exemple montrant que vous pouvez séparer la précision du nombre de chiffres affichés. La précision est à nouveau définie sur 2, mais 3 chiffres sont affichés, comme indiqué dans le dernier argument de la chaîne de contrôle de formatage:

Console.WriteLine("Rounded/formatted: " + 
  string.Format("{0:00}:{1:00}:{2:00}.{3:000}",
      roundedTimeSpan.Hours, roundedTimeSpan.Minutes,
      roundedTimeSpan.Seconds, roundedTimeSpan.Milliseconds));
// Input: 00:00:01.9365678
// Truncated: 00:00:01.9300000
// Rounded: 00:00:01.9400000
// Rounded/formatted: 00:00:01.940

MISE À JOUR 2010.01.06: une solution prête à l'emploi

Le matériel ci-dessus est utile si vous recherchez des idées. Depuis, j’ai eu le temps d’implémenter une solution intégrée pour ceux qui recherchent du code prêt à l’emploi.

Notez qu'il s'agit d'un code non commenté. La version entièrement commentée avec XML-doc-comments sera disponible dans ma bibliothèque à code source ouvert d'ici la fin du trimestre. . Bien que j’ai hésité à le poster " raw " Comme cela, je pense que cela pourrait toujours être avantageux pour les lecteurs intéressés.

Ce code améliore mon code au-dessus duquel, bien qu’il ait été arrondi, il restait 7 emplacements, remplis de zéros. Cette version finie arrondit et ajuste au nombre de chiffres spécifié.

Voici un exemple d'appel:

Console.Write(new RoundedTimeSpan(19365678, 2).ToString());
// Result = 00:00:01.94

Et voici le fichier complet RoundedTimeSpan.cs:

using System;

namespace CleanCode.Data
{
    public struct RoundedTimeSpan
    {

        private const int TIMESPAN_SIZE = 7; // it always has seven digits

        private TimeSpan roundedTimeSpan;
        private int precision;

        public RoundedTimeSpan(long ticks, int precision)
        {
            if (precision < 0) { throw new ArgumentException("precision must be non-negative"); }
            this.precision = precision;
            int factor = (int)System.Math.Pow(10, (TIMESPAN_SIZE - precision));

            // This is only valid for rounding milliseconds-will *not* work on secs/mins/hrs!
            roundedTimeSpan = new TimeSpan(((long)System.Math.Round((1.0 * ticks / factor)) * factor));
        }

        public TimeSpan TimeSpan { get { return roundedTimeSpan; } }

        public override string ToString()
        {
            return ToString(precision);
        }

        public string ToString(int length)
        { // this method revised 2010.01.31
            int digitsToStrip = TIMESPAN_SIZE - length;
            string s = roundedTimeSpan.ToString();
            if (!s.Contains(".") && length == 0) { return s; }
            if (!s.Contains(".")) { s += "." + new string('0', TIMESPAN_SIZE); }
            int subLength = s.Length - digitsToStrip;
            return subLength < 0 ? "" : subLength > s.Length ? s : s.Substring(0, subLength);
        }
    }
}

MISE À JOUR 2010.02.01: la solution packagée est désormais disponible

Je viens de publier une nouvelle version de mes bibliothèques Open Source hier, plus tôt que prévu, y compris le RoundedTimeSpan que j'ai décrit ci-dessus. Le code est ici ; pour l'API, lancez ici , puis accédez à RoundedTimeSpan sous CleanCode.Data espace de noms. La bibliothèque CleanCode.DLL comprend le code présenté ci-dessus mais le fournit dans un package terminé. Notez que j'ai légèrement amélioré la méthode ToString (int) ci-dessus depuis que je l'ai publiée le 2010.01.06.

Autres conseils

TimeSpan est un peu plus qu'une enveloppe autour du membre "Ticks". Il est assez facile de créer un nouveau TimeSpan à partir d'une version arrondie des autres ticks de TimeSpan.

TimeSpan t1 = new TimeSpan(2345678);
Console.WriteLine(t1);
TimeSpan t2 = new TimeSpan(t1.Ticks - (t1.Ticks % 100000));
Console.WriteLine(t2);

donne:

00:00:00.2345678
00:00:00.2300000

Si vous voulez un TimeSpan, c'est un one-liner:

public static TimeSpan RoundSeconds( TimeSpan span, int nDigits ) {
    return TimeSpan.FromTicks( (long)( Math.Round( span.TotalSeconds, nDigits ) * TimeSpan.TicksPerSecond) );
    // A simpler one-liner, if not doing more than 3 digits (TimeSpan.FromSeconds rounds to nearest millisecond):
    //return TimeSpan.FromSeconds( Math.Round( span.TotalSeconds, nDigits ) );
}

Si vous voulez une chaîne:

public static string RoundSecondsAsString( TimeSpan span, int nDigits ) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < nDigits; i++)
        sb.Append( "f" );
    return span.ToString( @"hh\:mm\:ss\." + sb );
}

Crédits:

La réponse de cc1960 montre l'utilisation de FromSeconds, mais il arrondit à la seconde entière. Ma réponse se généralise au nombre spécifié de chiffres.

La réponse d'Ed suggère d'utiliser une chaîne de format et inclut un lien vers le document de formatage.

Pour arrondir au multiple d'une autre unité, par exemple 1/30 de seconde:

    // Rounds span to multiple of "unitInSeconds".
    // NOTE: This will be close to the requested multiple,
    // but is not exact when unit cannot be exactly represented by a double.
    // e.g. "unitInSeconds = 1/30" isn't EXACTLY 1/30,
    // so the returned value won't be exactly a multiple of 1/30.
    public static double RoundMultipleAsSeconds( TimeSpan span, double unitInSeconds )
    {
        return unitInSeconds * Math.Round( span.TotalSeconds / unitInSeconds );
    }

    public static TimeSpan RoundMultipleAsTimeSpan( TimeSpan span, double unitInSeconds )
    {
        return TimeSpan.FromTicks( (long)(RoundMultipleAsSeconds( span, unitInSeconds ) * TimeSpan.TicksPerSecond) );

        // IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
        //return TimeSpan.FromSeconds( RoundMultipleAsSeconds( span, unitInSeconds ) );
    }

    // Rounds "span / n".
    // NOTE: This version might be a hair closer in some cases,
    // but probably not enough to matter, and can only represent units that are "1 / N" seconds.
    public static double RoundOneOverNAsSeconds( TimeSpan span, double n )
    {
        return Math.Round( span.TotalSeconds * n ) / n;
    }

    public static TimeSpan RoundOneOverNAsTimeSpan( TimeSpan span, double n )
    {
        return TimeSpan.FromTicks( (long)(RoundOneOverNAsSeconds( span, n ) * TimeSpan.TicksPerSecond) );

        // IF USE THIS: TimeSpan.FromSeconds rounds the result to nearest millisecond.
        //return TimeSpan.FromSeconds( RoundOneOverNAsSeconds( span, n ) );
    }

Pour utiliser l'un de ces éléments pour arrondir à des multiples de 1/30 de seconde:

    private void Test()
    {
        long ticks = (long) (987.654321 * TimeSpan.TicksPerSecond);
        TimeSpan span = TimeSpan.FromTicks( ticks );
        TestRound( span, 30 );
        TestRound( TimeSpan.FromSeconds( 987 ), 30 );
    }

    private static void TestRound(TimeSpan span, int n)
    {
        var answer1 = RoundMultipleAsSeconds( span, 1.0 / n );
        var answer2 = RoundMultipleAsTimeSpan( span, 1.0 / n );
        var answer3 = RoundOneOverNAsSeconds( span, n );
        var answer4 = RoundOneOverNAsTimeSpan( span, n );
    }

Résultats affichés dans le débogueur:

// for 987.654321 seconds:
    answer1 987.66666666666663  double
    answer2 {00:16:27.6666666}  System.TimeSpan
    answer3 987.66666666666663  double
    answer4 {00:16:27.6666666}  System.TimeSpan

// for 987 seconds:
    answer1 987 double
    answer2 {00:16:27}  System.TimeSpan
    answer3 987 double
    answer4 {00:16:27}  System.TimeSpan

Etant donné certains commentaires sur l'arrondi en secondes, j'ai pensé qu'il serait bien d'arrondir à TimeSpan:

public static TimeSpan Round(this TimeSpan ts, TimeSpan rnd) {
    if (rnd == TimeSpan.Zero)
        return ts;
    else {
        var rndticks = rnd.Ticks;
        var ansTicks = ts.Ticks + rndticks / 2;
        return TimeSpan.FromTicks(ansTicks - ansTicks % rndticks);
    }
}
public static TimeSpan Round(this TimeSpan ts) => ts.Round(TimeSpan.FromSeconds(1));

Étant donné les inexactitudes potentielles en arrondissant les graduations lorsque vous utilisez des unités fractionnaires (selon @ToolmakerSteve), j'ajoute une option d'arrondi fractionnaire lorsque vous avez besoin d'une plus grande précision et que vous êtes arrondi à une fraction d'ordinateur en secondes:

public static TimeSpan RoundToFraction(this TimeSpan ts, long num, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)Math.Round(Math.Round((double)ts.Ticks * (double)den / num / TimeSpan.TicksPerSecond) * (double)num / den * TimeSpan.TicksPerSecond));
public static TimeSpan RoundToFraction(this TimeSpan ts, long den) => (den == 0.0) ? TimeSpan.Zero : TimeSpan.FromTicks((long)(Math.Round((double)ts.Ticks * den / TimeSpan.TicksPerSecond) / den * TimeSpan.TicksPerSecond));
new TimeSpan(tmspan.Hours, tmspan.Minutes, tmspan.Seconds, (int)Math.Round(Convert.ToDouble(tmspan.Milliseconds / 10)));

Vous n'êtes pas sûr de TimeSpan, mais vous pouvez vérifier cette publication sur DateTimes:
http://mikeinmadison.wordpress.com/2008/12/datetimeround/

Voici une belle méthode d'extension:

    public static TimeSpan RoundToSeconds(this TimeSpan timespan, int seconds = 1)
    {
        long offset = (timespan.Ticks >= 0) ? TimeSpan.TicksPerSecond / 2 : TimeSpan.TicksPerSecond / -2;
        return TimeSpan.FromTicks((timespan.Ticks + offset) / TimeSpan.TicksPerSecond * TimeSpan.TicksPerSecond);
    }

Et voici quelques exemples:

DateTime dt1 = DateTime.Now.RoundToSeconds();  // round to full seconds
DateTime dt2 = DateTime.Now.RoundToSeconds(5 * 60);  // round to full 5 minutes

Ma solution:

    static TimeSpan RoundToSec(TimeSpan ts)
    {
        return TimeSpan.FromSeconds((int)(ts.TotalSeconds));
    }

Encore un autre moyen d'arrondir les millisecondes à la seconde près.

private const long TicksPer1000Milliseconds = 1000 * TimeSpan.TicksPerMillisecond;

// Round milliseconds to nearest second
// To round up, add the sub-second ticks required to reach the next second
// To round down, subtract the sub-second ticks
elapsedTime = new TimeSpan(elapsedTime.Ticks + (elapsedTime.Milliseconds >= 500 ? TicksPer1000Milliseconds - (elapsedTime.Ticks % TicksPer1000Milliseconds) : -(elapsedTime.Ticks % TicksPer1000Milliseconds)));

Une méthode d'extension si vous devez utiliser DateTime à la place, mais souhaitez quand même arrondir l'heure. Dans mon cas, je voulais arrondir à la minute.

public static DateTime RoundToMinute(this DateTime date)
{
    var roundedTimeSpan = TimeSpan.FromMinutes(Math.Round(date.TimeOfDay.TotalMinutes));
    return new DateTime(date.Year, date.Month, date.Day, roundedTimeSpan.Hours, roundedTimeSpan.Minutes, 0);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top