Redondeo de objetos DateTime


105

Quiero redondear fechas / horas al intervalo más cercano para una aplicación de gráficos. Me gustaría una firma de método de extensión como la siguiente para que el redondeo se pueda lograr para cualquier nivel de precisión:

static DateTime Round(this DateTime date, TimeSpan span);

La idea es que si paso en un lapso de diez minutos, se redondeará al intervalo de diez minutos más cercano. No puedo entender la implementación y espero que alguno de ustedes haya escrito o usado algo similar antes.

Creo que una implementación de piso, techo o más cercana está bien.

¿Algunas ideas?

Editar: Gracias a @tvanfosson y @ShuggyCoUk, la implementación se ve así:

public static class DateExtensions {
    public static DateTime Round(this DateTime date, TimeSpan span) {
        long ticks = (date.Ticks + (span.Ticks / 2) + 1)/ span.Ticks;
        return new DateTime(ticks * span.Ticks);
    }
    public static DateTime Floor(this DateTime date, TimeSpan span) {
        long ticks = (date.Ticks / span.Ticks);
        return new DateTime(ticks * span.Ticks);
    }
    public static DateTime Ceil(this DateTime date, TimeSpan span) {
        long ticks = (date.Ticks + span.Ticks - 1) / span.Ticks;
        return new DateTime(ticks * span.Ticks);
    }
}

Y se llama así:

DateTime nearestHour = DateTime.Now.Round(new TimeSpan(1,0,0));
DateTime minuteCeiling = DateTime.Now.Ceil(new TimeSpan(0,1,0));
DateTime weekFloor = DateTime.Now.Floor(new TimeSpan(7,0,0,0));
...

¡Salud!


1
Algunas de las implementaciones aquí también podrían ayudar: stackoverflow.com/questions/766626/…
Matt Hamilton


3
No olvide agregar el DateTimeKind original a la fecha recién creada, por ejemplo: new DateTime (ticks * span.Ticks, date.Kind);
AM

Respuestas:


130

Piso

long ticks = date.Ticks / span.Ticks;

return new DateTime( ticks * span.Ticks );

Redondo (hacia arriba en el punto medio)

long ticks = (date.Ticks + (span.Ticks / 2) + 1)/ span.Ticks;

return new DateTime( ticks * span.Ticks );

Techo

long ticks = (date.Ticks + span.Ticks - 1)/ span.Ticks;

return new DateTime( ticks * span.Ticks );

5
Recientemente me encontré con un problema en el que DateTimeKind no se conservaba. El siguiente ajuste a la última línea de cada método ayudó en mi caso:return new DateTime(ticks * span.Ticks, date.Kind);
Peet

39

Esto le permitirá redondear a cualquier intervalo dado. También es un poco más rápido que dividir y luego multiplicar las garrapatas.

public static class DateTimeExtensions
{
  public static DateTime Floor(this DateTime dateTime, TimeSpan interval)
  {
    return dateTime.AddTicks(-(dateTime.Ticks % interval.Ticks));
  }

  public static DateTime Ceiling(this DateTime dateTime, TimeSpan interval)
  {
    var overflow = dateTime.Ticks % interval.Ticks;

    return overflow == 0 ? dateTime : dateTime.AddTicks(interval.Ticks - overflow);
  }

  public static DateTime Round(this DateTime dateTime, TimeSpan interval)
  {
    var halfIntervalTicks = (interval.Ticks + 1) >> 1;

    return dateTime.AddTicks(halfIntervalTicks - ((dateTime.Ticks + halfIntervalTicks) % interval.Ticks));
  }
}

11

También debe tener claro si desea que su redondeo:

  1. estar al principio, al final o a la mitad del intervalo
    • El inicio es el más fácil y, a menudo, el esperado, pero debe ser claro en su especificación inicial.
  2. Cómo quiere que se redondeen los casos límite.
    • normalmente solo es un problema si se redondea hacia la mitad en lugar de al final.
    • Dado que redondear a la mitad es un intento de una respuesta libre de sesgos, debe usar algo como Redondeo de banqueros técnicamente redondear a la mitad incluso para estar realmente libre de sesgos.

Es muy probable que realmente solo te importe el primer punto, pero en estas preguntas 'simples' el comportamiento resultante puede tener consecuencias de gran alcance a medida que lo usas en el mundo real (a menudo en los intervalos adyacentes a cero).

La solución de tvanfosson cubre todos los casos enumerados en 1. El ejemplo del punto medio está sesgado hacia arriba. Es dudoso que esto sea un problema de redondeo relacionado con el tiempo.


3

Simplemente use los Ticks, usándolo para dividir, piso / techo / redondear el valor y volver a multiplicarlo.


-2

Si solo desea redondear la hora al valor máximo

Console.WriteLine(DateTime.Now.ToString("M/d/yyyy hh:00:00"));

OP solicitó un DateTime como objeto de devolución.
aj.toulan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.