Zaokrąglanie obiektów DateTime

105

Chcę zaokrąglić daty / godziny do najbliższego przedziału dla aplikacji do tworzenia wykresów. Chciałbym mieć podpis metody rozszerzenia, jak poniżej, aby można było uzyskać zaokrąglenie dla dowolnego poziomu dokładności:

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

Chodzi o to, że jeśli przejdę w przedziale czasowym dziesięciu minut, zaokrągli to do najbliższych dziesięciu minut. Nie mogę się skupić na implementacji i mam nadzieję, że któryś z was napisał lub użył czegoś podobnego wcześniej.

Myślę, że podłoga, sufit lub najbliższa realizacja są w porządku.

Jakieś pomysły?

Edycja: Dzięki @tvanfosson i @ShuggyCoUk implementacja wygląda następująco:

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

I tak się nazywa:

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

Twoje zdrowie!

granat
źródło
1
Niektóre z implementacji tutaj również mogą pomóc: stackoverflow.com/questions/766626/ ...
Matt Hamilton
3
Nie zapomnij dodać oryginalnego DateTimeKind do nowo utworzonej daty, np .: new DateTime (ticks * span.Ticks, date.Kind);
AM

Odpowiedzi:

130

Piętro

long ticks = date.Ticks / span.Ticks;

return new DateTime( ticks * span.Ticks );

Zaokrąglij (w górę na środku)

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

return new DateTime( ticks * span.Ticks );

Sufit

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

return new DateTime( ticks * span.Ticks );
tvanfosson
źródło
5
Niedawno napotkałem problem polegający na tym, że DateTimeKind nie został zachowany. Poniższe poprawki do ostatniej linii w każdej metodzie pomogły w moim przypadku:return new DateTime(ticks * span.Ticks, date.Kind);
Peet
39

Pozwoli ci to zaokrąglić do dowolnego podanego przedziału. Jest to również nieco szybsze niż dzielenie, a następnie mnożenie kleszczy.

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));
  }
}
aj.toulan
źródło
11

Należy również jasno określić, czy chcesz, aby zaokrąglenie:

  1. być na początku, na końcu lub w środku interwału
    • start jest najłatwiejszy i często oczekiwany, ale powinieneś być jasny w swojej początkowej specyfikacji.
  2. Jak chcesz, aby przypadki graniczne były zaokrąglane.
    • zwykle jest to problem tylko wtedy, gdy zaokrąglasz do środka, a nie do końca.
    • Ponieważ zaokrąglanie do środka jest próbą uzyskania odpowiedzi wolnej od uprzedzeń, musisz użyć czegoś takiego jak zaokrąglanie bankierów, technicznie zaokrąglone do połowy, nawet, aby być naprawdę wolnym od uprzedzeń.

Jest całkiem prawdopodobne, że naprawdę zależy ci tylko na pierwszym punkcie, ale w tych `` prostych '' pytaniach wynikowe zachowanie może mieć daleko idące konsekwencje, ponieważ używasz go w prawdziwym świecie (często w odstępach czasu bliskich zeru)

Rozwiązanie tvanfosson obejmuje wszystkie przypadki wymienione w 1. Przykład punktu środkowego jest przesunięty w górę. Wątpliwe jest, aby byłby to problem związany z zaokrąglaniem czasu.

ShuggyCoUk
źródło
3

Po prostu użyj znaczników, używając ich do podzielenia, podłogi / sufitu / zaokrąglenia wartości i pomnóż ją z powrotem.

Lucero
źródło
-2

Jeśli chcesz zaokrąglić godzinę do wartości maksymalnej

Console.WriteLine(DateTime.Now.ToString("M/d/yyyy hh:00:00"));
Priyanka
źródło
OP zażądał DateTime jako obiektu zwracającego.
aj.toulan