C # DateTime.Now precisión

97

Me encontré con un comportamiento inesperado con DateTime.UtcNow mientras hacía algunas pruebas unitarias. Parece que cuando llama a DateTime.Now/UtcNow en rápida sucesión, parece devolverle el mismo valor durante un intervalo de tiempo más largo de lo esperado, en lugar de capturar incrementos de milisegundos más precisos.

Sé que hay una clase de cronómetro que sería más adecuada para realizar mediciones de tiempo precisas, pero tenía curiosidad por saber si alguien podría explicar este comportamiento en DateTime. ¿Existe una precisión oficial documentada para DateTime.Now (por ejemplo, con una precisión de 50 ms?)? ¿Por qué DateTime.Now sería menos preciso de lo que podrían manejar la mayoría de los relojes de CPU? ¿Quizás solo está diseñado para la CPU con el mínimo común denominador?

public static void Main(string[] args)
{
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int i=0; i<1000; i++)
    {
        var now = DateTime.Now;
        Console.WriteLine(string.Format(
            "Ticks: {0}\tMilliseconds: {1}", now.Ticks, now.Millisecond));
    }

    stopwatch.Stop();
    Console.WriteLine("Stopwatch.ElapsedMilliseconds: {0}",
        stopwatch.ElapsedMilliseconds);

    Console.ReadLine();
}
Andy White
fuente
¿Cuál es el intervalo durante el cual recupera el mismo valor?
ChrisF
Cuando ejecuté el código anterior, obtuve solo 3 valores únicos para tics y milisegundos, y el tiempo de cronómetro final fue de 147 ms, por lo que parece que en mi máquina solo tiene una precisión de alrededor de 50 ms ...
Andy White
Debo decir que el bucle se ejecutó varias veces, pero solo vi 3 valores distintos ...
Andy White
Para cualquiera que venga aquí, aquí está el TL; DR // Use la función QueryPerformanceCounter "Recupera el valor actual del contador de rendimiento, que es una marca de tiempo de alta resolución (<1us) que puede usarse para mediciones de intervalo de tiempo". (Para obtener el código administrado, la clase System.Diagnostics.Stopwatch utiliza QPC como base de tiempo preciso.) Msdn.microsoft.com/en-us/library/windows/desktop/...
AnotherUser

Respuestas:

179

¿Por qué DateTime.Now sería menos preciso de lo que podrían manejar la mayoría de los relojes de CPU?

Un buen reloj debe ser preciso y exacto ; esos son diferentes. Como dice el viejo chiste, un reloj parado es exactamente exacto dos veces al día, un reloj un minuto lento nunca es exacto en ningún momento. Pero el reloj un minuto lento siempre es preciso al minuto más cercano, mientras que un reloj parado no tiene ninguna precisión útil.

¿Por qué la fecha y hora debería ser precisa , digamos un microsegundo, cuando no es posible que tenga una precisión del microsegundo? La mayoría de la gente no tiene ninguna fuente de señales horarias oficiales con una precisión de microsegundos. Por lo tanto, dar seis dígitos después del lugar decimal de precisión , los últimos cinco de los cuales son basura sería mentir .

Recuerde, el propósito de DateTime es representar una fecha y una hora . Los tiempos de alta precisión no son en absoluto el propósito de DateTime; como nota, ese es el propósito de StopWatch. El propósito de DateTime es representar una fecha y hora para propósitos como mostrar la hora actual al usuario, calcular el número de días hasta el próximo martes, y así sucesivamente.

En resumen, "¿qué hora es?" y "¿cuánto tiempo te llevó?" son preguntas completamente diferentes; no use una herramienta diseñada para responder una pregunta para responder a la otra.

Gracias por la pregunta; ¡Esto será un buen artículo de blog! :-)

Eric Lippert
fuente
2
@Eric Lippert: Raymond Chen tiene un viejo pero bueno en el tema de la diferencia entre "precisión" y "exactitud": blogs.msdn.com/oldnewthing/archive/2005/09/02/459952.aspx
jason
3
Bien, buen punto sobre precisión versus exactitud. Supongo que todavía no creo en la afirmación de que DateTime no es precisa porque "no tiene que ser así". Si tengo un sistema transaccional y quiero marcar una fecha y hora para cada registro, me parece intuitivo usar la clase DateTime, pero parece que hay componentes de hora más precisos / precisos en .NET, entonces ¿por qué sería DateTime menos capaz. Supongo que tendré que leer un poco más ...
Andy White
11
OK @Andy, suponga que tiene un sistema así. En una máquina, marca una transacción como ocurrida el 1 de enero, 12: 34: 30.23498273. En otra máquina de su clúster, marca una transacción como ocurrida el 1 de enero, 12: 34: 30.23498456. ¿Qué transacción ocurrió primero? A menos que sepa que los relojes de las dos máquinas están sincronizados con una diferencia de un microsegundo entre sí, no tiene idea de cuál ocurrió primero. La precisión adicional es basura engañosa . Si me saliera con la mía, todos los DateTimes se redondearían al segundo más cercano, como estaban en VBScript.
Eric Lippert
14
no es que esto resuelva el problema que mencionas, ya que las PC no sincronizadas promedio suelen estar fuera por minutos . Ahora bien, si redondear a 1 no resuelve nada, ¿por qué redondear? En otras palabras, no sigo su argumento de por qué los valores absolutos deberían tener una precisión menor que la precisión de las mediciones del tiempo delta.
Roman Starkov
3
Digamos que estoy creando un registro de actividad que requiere (1) saber cuándo ocurrió algo en términos de espacio de calendario (en unos pocos segundos) (2) saber muy exactamente el espacio entre eventos (dentro de 50 milisegundos aproximadamente). Parece que la apuesta más segura para esto sería usar DateTime. Ahora para la marca de tiempo de la primera acción, luego use un Cronómetro para acciones posteriores para determinar el desplazamiento desde el DateTime inicial. ¿Es este el enfoque que recomendarías, Eric?
devuxer
18

La precisión de DateTime es algo específica del sistema en el que se ejecuta. La precisión está relacionada con la velocidad de un cambio de contexto, que suele rondar los 15 o 16 ms. (En mi sistema, en realidad está a unos 14 ms de mi prueba, pero he visto algunas computadoras portátiles en las que está más cerca de una precisión de 35-40 ms).

Peter Bromberg escribió un artículo sobre sincronización de código de alta precisión en C #, que analiza esto.

Reed Copsey
fuente
2
Las 4 máquinas Win7 que he tenido a lo largo de los años han tenido una precisión de aproximadamente 1 ms. Now () sleep (1) Now () siempre resultó en un cambio de ~ 1ms en la fecha y hora cuando estaba probando.
Bengie
También veo la precisión de ~ 1 ms.
Timo
12

Me gustaría una fecha y hora precisa. Ahora :), así que preparé esto:

public class PreciseDatetime
{
    // using DateTime.Now resulted in many many log events with the same timestamp.
    // use static variables in case there are many instances of this class in use in the same program
    // (that way they will all be in sync)
    private static readonly Stopwatch myStopwatch = new Stopwatch();
    private static System.DateTime myStopwatchStartTime;

    static PreciseDatetime()
    {
        Reset();

        try
        {
            // In case the system clock gets updated
            SystemEvents.TimeChanged += SystemEvents_TimeChanged;
        }
        catch (Exception)
        {                
        }
    }

    static void SystemEvents_TimeChanged(object sender, EventArgs e)
    {
        Reset();
    }

    // SystemEvents.TimeChanged can be slow to fire (3 secs), so allow forcing of reset
    static public void Reset()
    {
        myStopwatchStartTime = System.DateTime.Now;
        myStopwatch.Restart();
    }

    public System.DateTime Now { get { return myStopwatchStartTime.Add(myStopwatch.Elapsed); } }
}
Palanqueta
fuente
2
Me gusta esta solución, pero no estaba seguro, así que hice mi propia pregunta ( stackoverflow.com/q/18257987/270348 ). Según el comentario / respuesta de Servy, nunca debes restablecer el cronómetro.
RobSiklos
1
Tal vez no en su contexto, pero restablecer tiene sentido en mi contexto; solo me aseguro de que se haga antes de que comience el tiempo.
Jimmy
No necesita la suscripción y no es necesario reiniciar el cronómetro. No es necesario ejecutar este código cada ~ 10ms y consume CPU. Y este código no es seguro para subprocesos en absoluto. Simplemente inicialice myStopwatchStartTime = DateTime.UtcNow; una vez, en el constructor estático.
VeganHunter
1
@VeganHunter No estoy seguro de haber entendido bien tu comentario, pero parece que piensas que TimeChanged se llama cada ~ 10ms. No es así.
Jimmy
@ Jimmy, tienes razón. Mi mal, entendí mal el código. El evento SystemEvents.TimeChanged se llama solo si el usuario cambia la hora del sistema. Es un evento raro.
VeganHunter
6

De MSDN encontrará que DateTime.Nowtiene una resolución aproximada de 10 milisegundos en todos los sistemas operativos NT.

La precisión real depende del hardware. Se puede obtener una mejor precisión utilizandoQueryPerformanceCounter .

Kevin Montrose
fuente
5

Por lo que vale, aparte de verificar realmente la fuente .NET, Eric Lippert proporcionó un comentario sobre esta pregunta SO diciendo que DateTime solo tiene una precisión de aproximadamente 30 ms. El razonamiento para no ser preciso en nanosegundos, en sus palabras, es que "no tiene por qué serlo".

Scott Anderson
fuente
4
Y podría ser peor. En VBScript, la función Now () redondea el resultado devuelto al segundo más cercano, despierto el hecho de que el valor devuelto tiene suficiente precisión disponible para ser preciso al microsegundo. En C #, la estructura se llama DateTime; está destinado a representar una fecha y una hora para los dominios no científicos típicos del mundo real, como cuándo expira su seguro de vida o cuánto tiempo ha pasado desde su último reinicio. No está diseñado para una sincronización de segundos de alta precisión.
Eric Lippert
3

De la documentación de MSDN :

La resolución de esta propiedad depende del temporizador del sistema.

También afirman que la resolución aproximada en Windows NT 3.5 y posterior es de 10 ms :)

Tomas Vana
fuente
1

La resolución de esta propiedad depende del temporizador del sistema, que depende del sistema operativo subyacente. Suele estar entre 0,5 y 15 milisegundos.

Como resultado, las llamadas repetidas a la propiedad Now en un intervalo de tiempo corto, como en un bucle, pueden devolver el mismo valor.

Enlace MSDN

Iman
fuente