¿Cómo puedo redondear el tiempo a los X minutos más cercanos?

160

¿Hay una simple función para redondear UP una DateTimea los 15 minutos más cercanos?

P.ej

2011-08-11 16:59 se convierte 2011-08-11 17:00

2011-08-11 17:00 se queda como 2011-08-11 17:00

2011-08-11 17:01 se convierte 2011-08-11 17:15

TimS
fuente

Respuestas:

287
DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime((dt.Ticks + d.Ticks - 1) / d.Ticks * d.Ticks, dt.Kind);
}

Ejemplo:

var dt1 = RoundUp(DateTime.Parse("2011-08-11 16:59"), TimeSpan.FromMinutes(15));
// dt1 == {11/08/2011 17:00:00}

var dt2 = RoundUp(DateTime.Parse("2011-08-11 17:00"), TimeSpan.FromMinutes(15));
// dt2 == {11/08/2011 17:00:00}

var dt3 = RoundUp(DateTime.Parse("2011-08-11 17:01"), TimeSpan.FromMinutes(15));
// dt3 == {11/08/2011 17:15:00}
dtb
fuente
13
Esta solución acaba de llegar a mi biblioteca de utilidades como método de extensión.
JYelton
1
Tenga cuidado con los tiempos de redondeo que están cerca del extremo superior. Esto puede provocar una excepción si los Ticks que calcula son mayores que DateTime.MaxValue.Ticks. Esté seguro y tome el mínimo de su valor calculado y DateTime.MaxValue.Ticks.
Paul Raff
44
¿No está perdiendo información del objeto DateTime con este método? ¿Como el tipo y la zona horaria, si están configurados?
Evren Kuzucuoglu
11
@ user14 .. El (+ d.Ticks - 1) se asegura de que se redondeará si es necesario. El / y * están redondeando. Ejemplo de la ronda 12 a los siguientes 5: (12 + 5 - 1) = 16, 16/5 = 3 (porque es un tipo de datos entero), 3 * 5 = 15. tada :)
Diego Frehner
12
@dtb una pequeña adición, de lo contrario, probablemente esté un poco molesto: debe mantener el tipo de fecha y hora ;-) DateTime RoundUp(DateTime dt, TimeSpan d) { return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks, dt.Kind); }
njy
107

Se le ocurrió una solución que no implica multiplicar y dividir long números.

public static DateTime RoundUp(this DateTime dt, TimeSpan d)
{
    var modTicks = dt.Ticks % d.Ticks;
    var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
    return new DateTime(dt.Ticks + delta, dt.Kind);
}

public static DateTime RoundDown(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta, dt.Kind);
}

public static DateTime RoundToNearest(this DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    bool roundUp = delta > d.Ticks / 2;
    var offset = roundUp ? d.Ticks : 0;

    return new DateTime(dt.Ticks + offset - delta, dt.Kind);
}

Uso:

var date = new DateTime(2010, 02, 05, 10, 35, 25, 450); // 2010/02/05 10:35:25
var roundedUp = date.RoundUp(TimeSpan.FromMinutes(15)); // 2010/02/05 10:45:00
var roundedDown = date.RoundDown(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00
var roundedToNearest = date.RoundToNearest(TimeSpan.FromMinutes(15)); // 2010/02/05 10:30:00
redent84
fuente
8
Pensé con certeza que esto sería más rápido que usar multiplicación y división, pero mis pruebas muestran que no lo es. Con más de 10000000 iteraciones, el método de módulo tardó ~ 610 ms en mi máquina, mientras que el método mult / div tardó ~ 500 ms. Creo que las FPU hacen que las preocupaciones de los viejos no sean un problema. Aquí está mi código de prueba: pastie.org/8610460
viggity
1
Gran uso de extensiones. ¡Gracias!
TravisWhidden
1
@Alovchin Gracias. He actualizado la respuesta. Creé esta ideone con su código para mostrar la diferencia: ideone.com/EVKFp5
redent84
1
Esto es bastante viejo, pero es el último %d.Ticksen RoundUpnecesario? d.Ticks - (dt.Ticks % d.Ticks))será necesariamente menor que d.Ticks, por lo que la respuesta debería ser la misma correcta?
Nate Diamond
1
Solo señalando, el módulo requiere una operación de división en la CPU. Pero estoy de acuerdo en que es más elegante que usar la propiedad de enrutamiento de divisiones enteras.
Alex
19

si necesita redondear al intervalo de tiempo más cercano (no hacia arriba), le sugiero que use lo siguiente

    static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
    {
        int f=0;
        double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
        if (m >= 0.5)
            f=1;            
        return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
    }
DevSal
fuente
Esta respuesta no se redondea correctamente. user1978424 tiene la única publicación que muestra correctamente cómo redondear al intervalo más cercano a continuación: (irónicamente rechazado porque la pregunta era bastante redondeada)
stitty
8
void Main()
{
    var date1 = new DateTime(2011, 8, 11, 16, 59, 00);
    date1.Round15().Dump();

    var date2 = new DateTime(2011, 8, 11, 17, 00, 02);
    date2.Round15().Dump();

    var date3 = new DateTime(2011, 8, 11, 17, 01, 23);
    date3.Round15().Dump();

    var date4 = new DateTime(2011, 8, 11, 17, 00, 00);
    date4.Round15().Dump();
}

public static class Extentions
{
    public static DateTime Round15(this DateTime value)
    {   
        var ticksIn15Mins = TimeSpan.FromMinutes(15).Ticks;

        return (value.Ticks % ticksIn15Mins == 0) ? value : new DateTime((value.Ticks / ticksIn15Mins + 1) * ticksIn15Mins);
    }
}

Resultados:

8/11/2011 5:00:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:15:00 PM
8/11/2011 5:00:00 PM
Vlad Bezden
fuente
3
2011-08-11 17:00:01se trunca a2011-08-11 17:00:00
JYelton
1
@JYelton: Gracias por señalar +1. Cambié mi código para acomodar eso.
Vlad Bezden
Proporcionar su formato de código Linqpad para una verificación fácil es un gran ahorro de tiempo. Muy facil de usar.
Adam Garner
6

Como odio reinventar la rueda, probablemente seguiría este algoritmo para redondear un valor DateTime a un incremento de tiempo especificado (Timespan):

  • Convierta el DateTimevalor que se redondeará en un valor decimal de coma flotante que represente el número total y fraccionario de TimeSpanunidades.
  • Redondea eso a un entero, usando Math.Round().
  • Vuelva a escalar a los ticks multiplicando el número entero redondeado por el número de ticks en la TimeSpanunidad.
  • Instancia un nuevo DateTimevalor del número redondeado de ticks y devuélvelo a la persona que llama.

Aquí está el código:

public static class DateTimeExtensions
{

    public static DateTime Round( this DateTime value , TimeSpan unit )
    {
        return Round( value , unit , default(MidpointRounding) ) ;
    }

    public static DateTime Round( this DateTime value , TimeSpan unit , MidpointRounding style )
    {
        if ( unit <= TimeSpan.Zero ) throw new ArgumentOutOfRangeException("unit" , "value must be positive") ;

        Decimal  units        = (decimal) value.Ticks / (decimal) unit.Ticks ;
        Decimal  roundedUnits = Math.Round( units , style ) ;
        long     roundedTicks = (long) roundedUnits * unit.Ticks ;
        DateTime instance     = new DateTime( roundedTicks ) ;

        return instance ;
    }

}
Nicholas Carey
fuente
Este es el código agradable para el redondeo al más cercano DateTime , pero también quiero la capacidad de ronda hasta a un múltiplo de unit . Pasar MidpointRounding.AwayFromZeroa Roundno tiene el efecto deseado. ¿Tienes algo más en mente al aceptar una MidpointRoundingdiscusión?
HappyNomad
2

Mi version

DateTime newDateTimeObject = oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);

Como método se bloquearía así

public static DateTime GetNextQuarterHour(DateTime oldDateTimeObject)
{
    return oldDateTimeObject.AddMinutes(15 - oldDateTimeObject.Minute % 15);
}

y se llama así

DateTime thisIsNow = DateTime.Now;
DateTime nextQuarterHour = GetNextQuarterHour(thisIsNow);
hombre de alma
fuente
esto no cuenta por segundos
Alex Norcliffe
1

¿Elegante?

dt.AddSeconds(900 - (x.Minute * 60 + x.Second) % 900)
Olaf
fuente
1
Una versión más correcta sería: x.AddSeconds (900 - (x.AddSeconds (-1) .Minute * 60 + x.AddSeconds (-1) .Second)% 900) .AddSeconds (-1), que se encarga de la condición "se queda".
Olaf
1

Precaución: la fórmula anterior es incorrecta, es decir, la siguiente:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks - 1) / d.Ticks) * d.Ticks);
}

debe reescribirse como:

DateTime RoundUp(DateTime dt, TimeSpan d)
{
    return new DateTime(((dt.Ticks + d.Ticks/2) / d.Ticks) * d.Ticks);
}
usuario1978424
fuente
1
Estoy en desacuerdo. Dado que la división entera se / d.Ticksredondea al intervalo de 15 minutos más cercano (llamemos a estos "bloques"), agregar solo medio bloque no garantiza el redondeo. Considera cuando tienes 4.25 bloques. Si agrega 0.5 bloques, luego pruebe cuántos bloques enteros tiene, todavía tiene 4. Agregar una marca menos que un bloque completo es la acción correcta. Asegura que siempre te muevas al siguiente rango de bloque (antes de redondear hacia abajo), pero evita que te muevas entre bloques exactos. (IE, si agregaste un bloque completo a 4.0 bloques, 5.0 se redondearía a 5, cuando quieras 4. 4.99 será 4.)
Brendan Moore
1

Una solución más detallada, que utiliza módulo y evita cálculos innecesarios.

public static class DateTimeExtensions
{
    public static DateTime RoundUp(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, true);
    }

    public static DateTime RoundDown(this DateTime dt, TimeSpan ts)
    {
        return Round(dt, ts, false);
    }

    private static DateTime Round(DateTime dt, TimeSpan ts, bool up)
    {
        var remainder = dt.Ticks % ts.Ticks;
        if (remainder == 0)
        {
            return dt;
        }

        long delta;
        if (up)
        {
            delta = ts.Ticks - remainder;
        }
        else
        {
            delta = -remainder;
        }

        return dt.AddTicks(delta);
    }
}
Bo Sunesen
fuente
0

Esta es una solución simple para redondear al minuto más cercano. Conserva la información de TimeZone y Kind de DateTime. Se puede modificar para adaptarse a sus propias necesidades (si necesita redondear a los 5 minutos más cercanos, etc.).

DateTime dbNowExact = DateTime.Now;
DateTime dbNowRound1 = (dbNowExact.Millisecond == 0 ? dbNowExact : dbNowExact.AddMilliseconds(1000 - dbNowExact.Millisecond));
DateTime dbNowRound2 = (dbNowRound1.Second == 0 ? dbNowRound1 : dbNowRound1.AddSeconds(60 - dbNowRound1.Second));
DateTime dbNow = dbNowRound2;
dodgy_coder
fuente
0

Puede usar este método, usa la fecha especificada para garantizar que mantenga el tipo de globalización y fecha y hora especificado previamente en el objeto de fecha y hora.

const long LNG_OneMinuteInTicks = 600000000;
/// <summary>
/// Round the datetime to the nearest minute
/// </summary>
/// <param name = "dateTime"></param>
/// <param name = "numberMinutes">The number minute use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, int numberMinutes = 1)
{
    long roundedMinutesInTicks = LNG_OneMinuteInTicks * numberMinutes;
    long remainderTicks = dateTime.Ticks % roundedMinutesInTicks;
    if (remainderTicks < roundedMinutesInTicks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundedMinutesInTicks - remainderTicks);
}

Prueba de violín .Net

Si desea usar el TimeSpan para redondear, puede usar esto.

/// <summary>
/// Round the datetime
/// </summary>
/// <example>Round(dt, TimeSpan.FromMinutes(5)); => round the time to the nearest 5 minutes.</example>
/// <param name = "dateTime"></param>
/// <param name = "roundBy">The time use to round the time to</param>
/// <returns></returns>        
public static DateTime Round(DateTime dateTime, TimeSpan roundBy)
{            
    long remainderTicks = dateTime.Ticks % roundBy.Ticks;
    if (remainderTicks < roundBy.Ticks / 2)
    {
        // round down
        return dateTime.AddTicks(-remainderTicks);
    }

    // round up
    return dateTime.AddTicks(roundBy.Ticks - remainderTicks);
}

TimeSpan Fiddle

Du D.
fuente
¿Qué sucede si desea redondear al séptimo minuto más cercano var d = new DateTime(2019, 04, 15, 9, 40, 0, 0);// debería ser 9:42 pero ninguno de estos métodos funciona así?
DotnetShadow
Editar parece que la respuesta de @soulflyman produciría el resultado correcto
DotnetShadow