Pergunta

Você pode arredondar um objeto .NET TimeSpan?

Eu tenho um valor Timespan de: 00: 00: 00,6193789

Existe uma maneira simples para mantê-lo um objeto TimeSpan mas rodada lo para algo como
00: 00: 00,62

Foi útil?

Solução

Desculpe, pessoal, mas ambos a pergunta ea resposta popular, até agora está errado: -)

A questão é errado porque Tyndall pede uma maneira de redondos , mas mostra um exemplo de truncamento .

A resposta de Will Dean é errado, porque ele também aborda truncamento em vez de arredondamento . (Suponho que alguém poderia argumentar que a resposta é certa para uma das duas perguntas, mas vamos filosofia licença de lado por enquanto ...)

Aqui está uma técnica simples para arredondamento :

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

Com o valor de entrada e número de dígitos do código de exemplo, esta é a saída:

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

Alterar a precisão de 2 dígitos a 5 dígitos e obter este em vez disso:

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

E mesmo alterá-lo para 0 para obter este resultado:

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

Finalmente, se você quer apenas um pouco mais de controle sobre a saída, adicione um pouco de formatação. Aqui está um exemplo, mostrando que você pode separar a precisão do número de dígitos exibidos. A precisão é novamente definido para 2, mas 3 dígitos são exibidos, conforme especificado no último argumento da string de controle de formatação:

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

2010.01.06 UPDATE: um out-of-the-box solução

O material acima é útil se você estiver à procura de ideias; Desde então tive tempo para implementar uma solução integrada para aqueles que procuram código de pronto-a-uso.

Note que este código é comentada. A versão totalmente comentado com XML-doc-comentários estarão disponíveis em minha open source biblioteca até o final do trimestre . Embora eu hesitou em publicá-la "raw" como este, eu figura que poderia ainda ser de algum benefício aos leitores interessados.

Este código melhora no meu código acima que, embora arredondada, ainda mostrou 7 lugares, preenchidos com zeros. Este acabados rodadas de versão e guarnições para o número especificado de dígitos.

Aqui está uma invocação de exemplo:

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

E aqui está o arquivo RoundedTimeSpan.cs completa:

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

2010.02.01 UPDATE: Embalado solução já está disponível

Eu só lançou uma nova versão das minhas bibliotecas de código aberto ontem, mais cedo do que o previsto, incluindo o RoundedTimeSpan eu descrevi acima. Código é aqui ; para a API começar aqui seguida, navegue até RoundedTimeSpan sob o namespace CleanCode.Data. A biblioteca CleanCode.DLL inclui o código mostrado acima, mas fornece-o numa embalagem acabada. Note que eu fiz uma ligeira melhoria no método ToString(int) acima desde que eu postei isso na 2010.01.06.

Outras dicas

TimeSpan é pouco mais do que um invólucro em torno do membro 'Carrapatos'. É muito fácil criar um novo TimeSpan a partir de uma versão arredondada de carrapatos de outro TimeSpan.

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

Dá:

00:00:00.2345678
00:00:00.2300000

Se você quiser um TimeSpan, é uma 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 ) );
}

Se você quer uma string:

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éditos:

mostra resposta de cc1960 usar de FromSeconds, mas ele arredondados para inteiros segundos. Minha resposta generaliza para o número especificado de dígitos.

A resposta de Ed sugere o uso de uma seqüência de formato, e inclui um link para o documento formatação.


Para redonda para múltiplo de alguma outra unidade, como 1/30 segundos:

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

Para usar um deles para arredondar para múltiplos de 1/30 segundos:

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

Os resultados vistos no depurador:

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

Dado alguns dos comentários sobre o arredondamento para segundos, pensei que o arredondamento para qualquer TimeSpan seria bom:

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

Dadas as potenciais imprecisões arredondamento para carrapatos quando se lida com unidades fracionárias (por @ToolmakerSteve), eu estou adicionando uma opção de arredondamento fracionário para quando você precisar maior precisão e são arredondados para um computador segundos fracionários:

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

Não tenho certeza TimeSpan, mas você pode verificar este post sobre DateTimes:
http://mikeinmadison.wordpress.com/2008/03/12/datetimeround/

Aqui está uma boa Extensão-Método:

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

E aqui estão alguns exemplos:

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

A minha solução:

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

No entanto, outra maneira de milissegundos e volta para o segundo mais próximo.

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

Um método de extensão, se você precisa trabalhar com DateTime vez, mas ainda deseja arredondar o tempo. No meu caso, eu queria rodada para o minuto.

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);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top