Округление объектов DateTime

Я хочу округлить дату / время до ближайшего интервала для приложения для построения диаграмм. Я хотел бы, чтобы подпись метода расширения была следующей, чтобы округление можно было получить для любого уровня точности:

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

Идея состоит в том, что если я пройду через промежуток времени в десять минут, он округлится до ближайшего десятиминутного интервала. Я не могу разобраться с реализацией и надеюсь, что кто-то из вас написал или использовал что-то подобное раньше.

Я думаю, что пол, потолок или ближайшая реализация в порядке.

Есть идеи?

Изменить: Благодаря @tvanfosson & @ShuggyCoUk, реализация выглядит следующим образом:

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

И называется так:

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

Ура!

5 ответов

Решение

Этаж

long ticks = date.Ticks / span.Ticks;

return new DateTime( ticks * span.Ticks );

Круглый (вверх по средней точке)

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

return new DateTime( ticks * span.Ticks );

потолок

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

return new DateTime( ticks * span.Ticks );

Это позволит вам округлить до любого заданного интервала. Это также немного быстрее, чем деление, а затем умножение тиков.

private static DateTime Floor(DateTime dateTime, TimeSpan interval)
{
  return dateTime.AddTicks(-(dateTime.Ticks % interval.Ticks));
}

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

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

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

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

Вам также должно быть ясно, хотите ли вы, чтобы ваше округление:

  1. быть в начале, конце или середине интервала
    • начало является самым простым и часто ожидаемым, но вы должны быть ясны в своей первоначальной спецификации.
  2. Как вы хотите, чтобы граничные случаи округлялись.
    • обычно проблема только в том случае, если вы округлились до середины, а не до конца.
    • Поскольку округление до середины - это попытка получить свободный от смещения ответ, вам нужно использовать что-то вроде банковского округления с технической точки зрения до половины, даже чтобы быть по-настоящему свободным от смещения.

Вполне вероятно, что вы действительно заботитесь только о первом пункте, но в этих "простых" вопросах результирующее поведение может иметь далеко идущие последствия, поскольку вы используете его в реальном мире (часто с интервалами, близкими к нулю)

Решение tvanfosson охватывает все случаи, перечисленные в 1. Пример средней точки смещен вверх. Сомнительно, что это было бы проблемой во временном округлении.

Просто используйте галочки, используя это, чтобы разделить, основать / потолок / округлить значение и умножить его обратно.

Если вы просто хотите округлить значение часа до потолка

Console.WriteLine(DateTime.Now.ToString("M/d/yyyy hh:00:00"));
Другие вопросы по тегам