Arredondando objetos DateTime


105

Desejo arredondar as datas / horas para o intervalo mais próximo para um aplicativo de gráficos. Eu gostaria de uma assinatura de método de extensão como a que se segue, para que o arredondamento possa ser obtido para qualquer nível de precisão:

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

A ideia é que, se eu passar em um intervalo de tempo de dez minutos, ele será arredondado para o intervalo de dez minutos mais próximo. Não consigo entender a implementação e espero que um de vocês tenha escrito ou usado algo semelhante antes.

Eu acho que um piso, teto ou a implementação mais próxima está bom.

Alguma ideia?

Edit: Graças a @tvanfosson & @ShuggyCoUk, a implementação é semelhante a esta:

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);
    }
}

E é chamado assim:

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));
...

Felicidades!


1
Algumas das implementações aqui também podem ajudar: stackoverflow.com/questions/766626/…
Matt Hamilton


3
Não se esqueça de adicionar o DateTimeKind original à data recém-criada, ex: new DateTime (ticks * span.Ticks, date.Kind);
AM

Respostas:


130

Chão

long ticks = date.Ticks / span.Ticks;

return new DateTime( ticks * span.Ticks );

Rodada (para cima no ponto médio)

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

return new DateTime( ticks * span.Ticks );

Teto

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

return new DateTime( ticks * span.Ticks );

5
Recentemente, tive um problema em que DateTimeKind não foi preservado. O seguinte ajuste até a última linha em cada método ajudou no meu caso:return new DateTime(ticks * span.Ticks, date.Kind);
Peet

39

Isso permitirá que você faça o arredondamento para qualquer intervalo determinado. Também é um pouco mais rápido do que dividir e multiplicar os carrapatos.

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

Você também deve deixar claro se deseja que o arredondamento:

  1. seja no início, fim ou meio do intervalo
    • iniciar é o mais fácil e geralmente o esperado, mas você deve ser claro em suas especificações iniciais.
  2. Como você deseja que os casos limites sejam arredondados.
    • normalmente, apenas um problema se você estiver arredondando para o meio e não para o fim.
    • Já que o arredondamento para o meio é uma tentativa de uma resposta livre de parcialidade, você precisa usar algo como o arredondamento de Bankers, tecnicamente arredondado para a metade, para estar realmente livre de parcialidade.

É bem provável que você realmente se preocupe apenas com o primeiro ponto, mas nessas questões 'simples' o comportamento resultante pode ter consequências de longo alcance, conforme você o usa no mundo real (muitas vezes em intervalos adjacentes a zero)

A solução de tvanfosson cobre todos os casos listados em 1. O exemplo do ponto médio é polarizado para cima. É duvidoso que isso seja um problema de arredondamento relacionado ao tempo.


3

Basta usar os Ticks, usando-os para dividir, andar / teto / arredondar o valor e multiplicar de volta.


-2

Se você quiser apenas arredondar o valor da hora para o teto

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

OP solicitou um DateTime como o objeto de retorno.
aj.toulan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.