Cómo truncar milisegundos de un .NET DateTime

334

Estoy tratando de comparar una marca de tiempo de una solicitud entrante a un valor almacenado de la base de datos. SQL Server, por supuesto, mantiene cierta precisión de milisegundos en el tiempo, y cuando se lee en un .NET DateTime, incluye esos milisegundos. Sin embargo, la solicitud entrante al sistema no ofrece esa precisión, por lo que simplemente necesito soltar los milisegundos.

Siento que me falta algo obvio, pero no he encontrado una manera elegante de hacerlo (C #).

Jeff Putz
fuente
(3er intento ...) Dado que el 20% de las respuestas ( 1 , 2 , 3 ) describen cómo omitir o eliminar el componente de milisegundos de la stringrepresentación formateada de a DateTime, tal vez sea necesaria una edición para dejar en claro que "truncar" / "drop" milisegundos medios "producen un DateTimevalor en el que todos los componentes de fecha / hora son los mismos excepto TimeOfDay.TotalMillisecondses 0". La gente no lee, por supuesto, sino solo para eliminar cualquier ambigüedad.
TOCINO

Respuestas:

557

Lo siguiente funcionará para un DateTime que tiene milisegundos fraccionales y también conserva la propiedad Kind (Local, Utc o Undefined).

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

o el equivalente y más corto:

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

Esto podría generalizarse en un método de extensión:

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 se usa de la siguiente manera:

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
fuente
Si bien le daré esto porque es técnicamente correcto, para las personas que leen datos de SQL Server para compararlos con algunos datos distribuidos (una solicitud basada en la Web, en mi caso), esta cantidad de resolución no es necesaria.
Jeff Putz
1
Agradable. Claramente, alguien necesita darle a la clase DateTime algunos métodos de extensión para redondear a lo más cercano posible para que este tipo de buena codificación se reutilice.
chris.w.mclean
Esto es muy poco probable, pero ¿este enfoque no se rompe cuando los ticks = 0?
adotout
@adotout, el método Truncar anterior arrojará una DivideByZeroException si el parámetro timeSpan es cero, ¿es esto lo que quiere decir con "enfoque se rompe cuando ticks = 0"? Sería mejor lanzar una excepción ArgumentException cuando timeSpan es cero.
Joe
145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
benPearce
fuente
34
Claro y simple, solo recuerde agregar un ", date.Kind" al final del constructor para asegurarse de que no pierda una información importante.
JMcDaniel
99
Tenga cuidado con esta solución en el código sensible al rendimiento. Mi aplicación estaba gastando el 12% del tiempo de CPU en System.DateTime.GetDatePart .
Coronel Panic
3
Es simple, pero más lento que la pregunta marcada como la mejor respuesta. No es que esto pueda ser un cuello de botella, pero es aproximadamente 7-8 veces más lento.
Jonas
Las declaraciones "mucho más lentas" no son exactamente correctas, la diferencia es entre 50% y aproximadamente 100% dependiendo del tiempo de ejecución; neto 4.7.2: 0.35 µs vs 0.62 µs y núcleo 3.1: 0.18 µs vs 0.12 µs que son microsegundos (10 ^ -6 segundos)
juwens
62

Aquí hay un método de extensión basado en una respuesta anterior que le permitirá truncar a cualquier resolución ...

Uso:

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

Clase:

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);
    }
}
Sanders del cielo
fuente
1
Esta es una solución realmente flexible y reutilizable, que es concisa y expresiva sin ser demasiado detallada. Mi voto como mejor solución.
Jaans
2
En realidad, no necesita los paréntesis alrededor del% operandos.
ErikE
8
.. pero los parens agregan claridad, en mi opinión.
orion elenzil
28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);
chris.w.mclean
fuente
70
-1: funcionará solo si el valor de DateTime no incluye fracciones de milisegundos.
Joe
77
El uso de este método hizo que algunas de las pruebas de mi unidad fallaran: Se esperaba: 2010-05-05 15: 55: 49.000 Pero fue: 2010-05-05 15: 55: 49.000. Supongo que debido a lo que Joe mencionó sobre fracciones de milisegundos.
Seth Reno
66
No funciona para la serialización, por ejemplo, 2010-12-08T11: 20: 03.000099 + 15: 00 es la salida, no corta completamente los milisegundos.
joedotnot
55
La Millisecondpropiedad proporciona un número entero entre 0 y 999 (inclusive). Entonces, si la hora del día anterior a la operación fue, digamos, 23:48:49.1234567entonces ese número entero será 123, y la hora del día después de la operación es 23:48:49.0004567. Por lo tanto, no se ha truncado a un número entero de segundos.
Jeppe Stig Nielsen
11

A veces desea truncar a algo basado en el calendario, como año o mes. Aquí hay un método de extensión que le permite elegir cualquier resolución.

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
fuente
9

En lugar de soltar los milisegundos y luego comparar, ¿por qué no comparar la diferencia?

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

o

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;
Beto
fuente
3
la primera opción no funciona, porque TotalSeconds es un doble; También devuelve los milisegundos.
Jowen
1
Comparar la diferencia no da el mismo resultado que truncar y luego comparar. Por ejemplo, 5.900 y 6.100 están separados por menos de un segundo, por lo que se compararían como iguales con su método. Pero los valores truncados 5 y 6 son diferentes. Lo que sea apropiado depende de su requerimiento.
Joe
7

Menos obvio pero más de 2 veces más 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
fuente
3
Tenga en cuenta que la segunda opción, d.AddMilliseconds(-d.Millisecond)no necesariamente mueve el DateTime exactamente al segundo completo anterior. d.Ticks % TimeSpan.TicksPerMillisecondlas garrapatas (entre 0 y 9,999) más allá de su segundo permanecerán.
Technetium
5

Para redondear a la segunda:

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

Reemplace con TicksPerMinutepara redondear al minuto.


Si su código es sensible al rendimiento, tenga cuidado con

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

Mi aplicación estaba gastando el 12% del tiempo de CPU en System.DateTime.GetDatePart .

Coronel Panic
fuente
3

Una forma de lectura fácil es ...

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

Y más...

//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
fuente
44
La conversión a cadenas y análisis es una idea horrible en términos de rendimiento.
Jeff Putz
2
@JeffPutz cierto, pero es simple. Adecuado para una prueba automatizada donde un valor insertado y extraído de un DB pierde los ticks (mi situación exacta). Sin embargo, esta respuesta podría ser aún más simple de lo que es, ya que var now = DateTime.Parse(DateTime.Now.ToString())funciona bien.
Grimm The Opiner
1
@GrimmTheOpiner - "... funciona bien", en su mayoría, pero no está garantizado. Lo que hace es: "Redondea un DateTime a cualquier precisión 'Long time' configurado como en las preferencias del Panel de control del usuario actual". Que suele ser, pero no necesariamente, segundos.
Joe
1
Al igual que su simplicidad, el rendimiento no es un problema para la situación de pruebas automatizadas.
liang
1

Respecto a la respuesta de Diadistis. Esto funcionó para mí, excepto que tuve que usar Floor para eliminar la parte fraccional de la división antes de la multiplicación. Entonces,

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

se convierte

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

Hubiera esperado que la división de dos valores Long produjera un Long, eliminando así la parte decimal, pero lo resuelve como un Doble dejando exactamente el mismo valor después de la multiplicación.

Eppsia


fuente
1

2 Métodos de extensión para las soluciones mencionadas anteriormente

    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
fuente
1

No es la solución más rápida pero simple y fácil de entender:

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

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

Y por uso de

ToLongDateString() // its show 19 February 2016.

:PAGS

Dhawal Shukal
fuente
-1. Puedo ver cómo la pregunta podría haber sido mal interpretado como pidiendo una stringvez de una DateTime, pero esto omite componentes de tiempo de la salida por completo . (Eso también hace que el acceso a la Todaypropiedad sea innecesario.)
BACON
0

Nuevo método

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

// define el parámetro de paso de cadena dd-mmm-aaaa retorno 24-feb-2016

O se muestra en el cuadro de texto

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

// poner PageonLoad

Dhawal Shukal
fuente
-1. Puedo ver cómo la pregunta podría haber sido mal interpretado como pidiendo una stringvez de una DateTime, pero esto omite componentes de tiempo de la salida por completo . (Eso también hace que el acceso a la Todaypropiedad sea innecesario.)
BACON
0

En mi caso, tenía el objetivo de salvar TimeSpan de la herramienta datetimePicker sin guardar los segundos y los milisegundos, y aquí está la solución.

Primero convierta el datetimePicker.value a su formato deseado, el mío es "HH: mm" y luego vuelva a convertirlo a TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;
Campanita
fuente
Una mejor manera (intención más clara, evita formatear y volver a analizar desde a string) para hacer esto sería DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); O podría usar una variación del método de extensión de Joe que funciona con TimeSpanvalores y usar TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));para truncar segundos.
TOCINO
0

Esta es mi versión de los métodos de extensión publicados aquí y en preguntas similares. Esto valida el valor de los ticks de una manera fácil de leer y conserva el DateTimeKind de la instancia original de DateTime. (Esto tiene efectos secundarios sutiles pero relevantes cuando se almacena en una base de datos como MongoDB).

Si el verdadero objetivo es truncar un DateTime a un valor específico (es decir, horas / minutos / segundos / MS), recomiendo implementar este método de extensión en su código. Asegura que solo puede truncar con una precisión válida y conserva los metadatos importantes DateTimeKind de su instancia 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.");
}

Entonces puedes usar el método de esta manera:

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

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

Sé que la respuesta es bastante tardía, pero la mejor manera de deshacerse de milisegundos es

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

Intente imprimir el valor de la variable, mostrará la fecha y hora, sin milisegundos.

Nisarg Shah
fuente
1
Esto no es lo ideal. Entonces tienes una cadena y no una fecha y hora.
Jeff Putz