Como truncar milissegundos de um .NET DateTime

334

Estou tentando comparar um carimbo de data / hora de uma solicitação recebida com um valor armazenado no banco de dados. É claro que o SQL Server mantém alguma precisão de milissegundos no tempo e, quando lido em um .NET DateTime, inclui esses milissegundos. A solicitação de entrada para o sistema, no entanto, não oferece essa precisão, portanto, preciso simplesmente eliminar os milissegundos.

Sinto que estou perdendo algo óbvio, mas não encontrei uma maneira elegante de fazer isso (C #).

Jeff Putz
fonte
(3ª tentativa ...) Como 20% das respostas ( 1 , 2 , 3 ) descrevem como omitir ou remover o componente de milissegundos da stringrepresentação formatada de a DateTime, talvez seja necessária uma edição para deixar claro que "truncar" / "drop" milissegundos significa "produz um DateTimevalor em que todos os componentes de data / hora são os mesmos, exceto TimeOfDay.TotalMillisecondsé 0". As pessoas não leem, é claro, mas apenas para eliminar qualquer ambiguidade.
BACON

Respostas:

557

O seguinte funcionará para um DateTime que possui milissegundos fracionários e também preservará a propriedade Kind (Local, Utc ou Indefinido).

DateTime dateTime = ... anything ...
dateTime = new DateTime(
    dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), 
    dateTime.Kind
    );

ou o equivalente e menor:

dateTime = dateTime.AddTicks( - (dateTime.Ticks % TimeSpan.TicksPerSecond));

Isso pode ser generalizado em um método de extensão:

public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
    if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException
    if (dateTime == DateTime.MinValue || dateTime == DateTime.MaxValue) return dateTime; // do not modify "guard" values
    return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}

que é usado da seguinte maneira:

dateTime = dateTime.Truncate(TimeSpan.FromMilliseconds(1)); // Truncate to whole ms
dateTime = dateTime.Truncate(TimeSpan.FromSeconds(1)); // Truncate to whole second
dateTime = dateTime.Truncate(TimeSpan.FromMinutes(1)); // Truncate to whole minute
...
Joe
fonte
Embora eu dê a você isso porque você está tecnicamente correto, para que as pessoas que lêem dados do SQL Server comparem com alguns dados distribuídos (uma solicitação baseada na Web, no meu caso), essa quantidade de resolução não é necessária.
21411 Jeff Putz
1
Agradável. É claro que alguém precisa fornecer à classe DateTime alguns métodos de extensão para arredondar para o mais próximo possível, para que esse tipo de boa codificação seja reutilizado.
chris.w.mclean
Isso é muito improvável, mas essa abordagem não é interrompida quando ticks = 0?
adotout 24/07
@adotout, o método Truncate acima lançará uma DivideByZeroException se o parâmetro timeSpan for zero, é isso que você quer dizer com "quebra de abordagem quando ticks = 0"? Seria melhor lançar uma ArgumentException quando timeSpan for zero.
31413 Joe
145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
benPearce
fonte
34
Clara e simples, lembre-se de adicionar um ", date.Kind" ao final do construtor para garantir que você não perca informações importantes.
JMcDaniel
9
Seja cauteloso com esta solução em código sensível ao desempenho. Meu aplicativo estava gastando 12% do tempo da CPU em System.DateTime.GetDatePart .
Coronel Panic
3
É simples, mas mais lento que a pergunta marcada como melhor resposta. Não que isso possa ser um gargalo, mas é aproximadamente 7-8x mais lento.
Jonas
As instruções "muito mais lentas" não estão exatamente corretas, a diferença está entre 50% e cerca de 100%, dependendo do tempo de execução; net 4.7.2: 0.35µs vs 0.62 µs e core 3.1: 0.18 µs vs 0.12 µs que são microssegundos (10 ^ -6 segundos)
juwens
62

Aqui está um método de extensão baseado em uma resposta anterior que permitirá truncar para qualquer resolução ...

Uso:

DateTime myDateSansMilliseconds = myDate.Truncate(TimeSpan.TicksPerSecond);
DateTime myDateSansSeconds = myDate.Truncate(TimeSpan.TicksPerMinute)

Classe:

public static class DateTimeUtils
{
    /// <summary>
    /// <para>Truncates a DateTime to a specified resolution.</para>
    /// <para>A convenient source for resolution is TimeSpan.TicksPerXXXX constants.</para>
    /// </summary>
    /// <param name="date">The DateTime object to truncate</param>
    /// <param name="resolution">e.g. to round to nearest second, TimeSpan.TicksPerSecond</param>
    /// <returns>Truncated DateTime</returns>
    public static DateTime Truncate(this DateTime date, long resolution)
    {
        return new DateTime(date.Ticks - (date.Ticks % resolution), date.Kind);
    }
}
Sky Sanders
fonte
1
Essa é uma solução realmente flexível e reutilizável, concisa e expressiva sem ser excessivamente detalhada. Meu voto como melhor solução.
Jaans 17/09/2015
2
Você realmente não precisa dos parênteses em torno dos operandos%.
ErikE
8
.. mas os parens acrescentam clareza, na minha opinião.
orion elenzil 27/05
28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);
chris.w.mclean
fonte
70
-1: Funcionará apenas se o valor DateTime não incluir frações de milissegundos.
Joe
7
O uso desse método causou falha de alguns dos meus testes de unidade: Esperado: 05-05 de 2010 15: 55: 49.000 Mas foi: 05-05 de 2010 15: 55: 49.000. Eu estou supondo devido ao que Joe mencionou sobre frações de um milissegundo.
Seth Reno
6
Não funciona para serialização, por exemplo, 2010-12-08T11: 20: 03.000099 + 15: 00 é a saída, não corta completamente os milissegundos.
Joedotnot
5
A Millisecondpropriedade fornece um número inteiro entre 0 e 999 (inclusive). Portanto, se a hora do dia anterior à operação foi, digamos 23:48:49.1234567,, esse número inteiro será 123e a hora do dia após a operação 23:48:49.0004567. Portanto, não foi truncado por um número inteiro de segundos.
Jeppe Stig Nielsen
11

Às vezes, você deseja truncar para algo baseado em calendário, como ano ou mês. Aqui está um método de extensão que permite escolher qualquer resolução.

public enum DateTimeResolution
{
    Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}

public static DateTime Truncate(this DateTime self, DateTimeResolution resolution = DateTimeResolution.Second)
{
    switch (resolution)
    {
        case DateTimeResolution.Year:
            return new DateTime(self.Year, 1, 1, 0, 0, 0, 0, self.Kind);
        case DateTimeResolution.Month:
            return new DateTime(self.Year, self.Month, 1, 0, 0, 0, self.Kind);
        case DateTimeResolution.Day:
            return new DateTime(self.Year, self.Month, self.Day, 0, 0, 0, self.Kind);
        case DateTimeResolution.Hour:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerHour));
        case DateTimeResolution.Minute:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMinute));
        case DateTimeResolution.Second:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerSecond));
        case DateTimeResolution.Millisecond:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMillisecond));
        case DateTimeResolution.Tick:
            return self.AddTicks(0);
        default:
            throw new ArgumentException("unrecognized resolution", "resolution");
    }
}
KingPong
fonte
9

Em vez de reduzir os milissegundos e comparar, por que não comparar a diferença?

DateTime x; DateTime y;
bool areEqual = (x-y).TotalSeconds == 0;

ou

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;
Prumo
fonte
3
a primeira opção não funciona, porque TotalSeconds é um dobro; Ele também retorna os milissegundos.
Jowen
1
Comparar a diferença não dá o mesmo resultado que truncar e comparar. Por exemplo, 5.900 e 6.100 estão a menos de um segundo de distância, portanto, seria comparável com o seu método. Mas os valores truncados 5 e 6 são diferentes. Qual é apropriado depende de sua exigência.
Joe
7

Menos óbvio, mas duas vezes mais rápido:

// 10000000 runs

DateTime d = DateTime.Now;

// 484,375ms
d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);
Diadistis
fonte
3
Observe que a segunda opção d.AddMilliseconds(-d.Millisecond),, não necessariamente move o DateTime exatamente para o segundo inteiro anterior. d.Ticks % TimeSpan.TicksPerMillisecondos ticks (algo entre 0 e 9.999) além do segundo permanecerão.
Technetium
5

Para arredondar para o segundo:

dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerSecond)

Substitua por TicksPerMinutepara arredondar para o minuto.


Se seu código é sensível ao desempenho, tenha cuidado com

new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second)

Meu aplicativo estava gastando 12% do tempo da CPU em System.DateTime.GetDatePart .

Coronel Panic
fonte
3

Uma maneira de facilitar a leitura é ...

//Remove milliseconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss", null);

E mais...

//Remove seconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm"), "yyyy-MM-dd HH:mm", null);

//Remove minutes
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH"), "yyyy-MM-dd HH", null);

//and go on...
Sergio Cabral
fonte
4
Converter em strings e analisar é uma péssima idéia em termos de desempenho.
101315 Jeff Putz
2
@JeffPutz é verdade, mas é simples. Adequado para um teste automatizado em que um valor inserido e retirado de um banco de dados perde os pontos (minha situação exata). No entanto, essa resposta pode ser ainda mais simples do que é, pois var now = DateTime.Parse(DateTime.Now.ToString())funciona bem.
Grimm The Opiner
1
@GrimmTheOpiner - "... funciona muito bem", principalmente, mas não garantido. O que ele faz é: "Arredonda um DateTime para qualquer precisão 'Long time' configurada como nas preferências atuais do Painel de Controle do usuário". Que geralmente é, mas não necessariamente, segundos.
Joe Joe
1
como sua simplicidade, o desempenho não é um problema para a situação de teste automatizado.
Liang
1

Em relação à resposta de Diadistis. Isso funcionou para mim, exceto que eu tive que usar o Floor para remover a parte fracionária da divisão antes da multiplicação. Assim,

d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

torna-se

d = new DateTime(Math.Floor(d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

Eu esperava que a divisão de dois valores Long resultasse em um Long, removendo assim a parte decimal, mas a resolve como um Double, deixando exatamente o mesmo valor após a multiplicação.

Eppsia


fonte
1

2 Métodos de extensão para as soluções mencionadas acima

    public static bool LiesAfterIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate > compareDate;
    }


    public static bool LiesAfterOrEqualsIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate >= compareDate;
    }

uso:

bool liesAfter = myObject.DateProperty.LiesAfterOrEqualsIgnoringMilliseconds(startDateTime, DateTimeKind.Utc);
HerbalMart
fonte
1

Não é a solução mais rápida, mas simples e fácil de entender:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)
AlliterativeAlice
fonte
0
DateID.Text = DateTime.Today.ToShortDateString();

Use ToShortDateString() //Date 2-02-2016
Use ToShortDateString() // Time 

E pelo uso de

ToLongDateString() // its show 19 February 2016.

: P

Dhawal Shukal
fonte
-1. Eu posso ver como a questão poderia ter sido mal interpretado como pedir para produzir um stringem vez de um DateTime, mas isso omite componentes de tempo a partir da saída completamente . (Isso torna o acesso a Todaypropriedade desnecessária, também.)
BACON
0

Novo método

String Date = DateTime.Today.ToString("dd-MMM-yyyy"); 

// define o parâmetro de passagem da seqüência de caracteres dd-mmm-aaaa retorno 24 de fevereiro de 2016

Ou mostrado na caixa de texto

txtDate.Text = DateTime.Today.ToString("dd-MMM-yyyy");

// coloca em PageonLoad

Dhawal Shukal
fonte
-1. Eu posso ver como a questão poderia ter sido mal interpretado como pedir para produzir um stringem vez de um DateTime, mas isso omite componentes de tempo a partir da saída completamente . (Isso torna o acesso a Todaypropriedade desnecessária, também.)
BACON
0

No meu caso, eu pretendia salvar o TimeSpan da ferramenta datetimePicker sem salvar os segundos e os milissegundos, e aqui está a solução.

Primeiro converta o datetimePicker.value para o formato desejado, que é "HH: mm", depois converta-o novamente para TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;
Pensador Bell
fonte
Uma maneira melhor (intenção mais clara, evita a formatação e a análise de a string) de fazer isso seria DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Ou você poderia usar uma variação do método de extensão de Joe que opera com TimeSpanvalores e usa TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));para truncar segundos.
BACON
0

Esta é a minha versão dos métodos de extensão postados aqui e em perguntas semelhantes. Isso valida o valor dos ticks de uma maneira fácil de ler e preserva o DateTimeKind da instância original do DateTime. (Isso tem efeitos colaterais sutis, mas relevantes, ao armazenar em um banco de dados como o MongoDB.)

Se o verdadeiro objetivo é truncar um DateTime para um valor especificado (ou seja, Horas / Minutos / Segundos / MS), recomendo implementar esse método de extensão no seu código. Ele garante que você só pode truncar com uma precisão válida e preserva os metadados importantes DateTimeKind da sua instância original:

public static DateTime Truncate(this DateTime dateTime, long ticks)
{
    bool isValid = ticks == TimeSpan.TicksPerDay 
        || ticks == TimeSpan.TicksPerHour 
        || ticks == TimeSpan.TicksPerMinute 
        || ticks == TimeSpan.TicksPerSecond 
        || ticks == TimeSpan.TicksPerMillisecond;

    // /programming/21704604/have-datetime-now-return-to-the-nearest-second
    return isValid 
        ? DateTime.SpecifyKind(
            new DateTime(
                dateTime.Ticks - (dateTime.Ticks % ticks)
            ),
            dateTime.Kind
        )
        : throw new ArgumentException("Invalid ticks value given. Only TimeSpan tick values are allowed.");
}

Então você pode usar o método como este:

DateTime dateTime = DateTime.UtcNow.Truncate(TimeSpan.TicksPerMillisecond);

dateTime.Kind => DateTimeKind.Utc
Kyle L.
fonte
-1

Eu sei que a resposta é muito tarde, mas a melhor maneira de se livrar dos milissegundos é

var currentDateTime = DateTime.Now.ToString("s");

Tente imprimir o valor da variável, ela mostrará a data e hora, sem milissegundos.

Nisarg Shah
fonte
1
Isto não é o ideal. Você tem uma sequência e não um DateTime.
101319 Jeff Putz