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

105

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

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

Ура!

граната
источник
1
Некоторые из реализаций здесь тоже могут помочь: stackoverflow.com/questions/766626/…
Мэтт Гамильтон
3
Не забудьте добавить исходный DateTimeKind к вновь созданной дате ex: new DateTime (ticks * span.Ticks, date.Kind);
AM

Ответы:

130

Пол

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 );
tvanfosson
источник
5
Недавно я столкнулся с проблемой, когда DateTimeKind не сохранился. В моем случае помогла следующая настройка последней строки каждого метода:return new DateTime(ticks * span.Ticks, date.Kind);
Peet
39

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

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

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

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

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

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

ShuggyCoUk
источник
3

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

Lucero
источник
-2

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

Console.WriteLine(DateTime.Now.ToString("M/d/yyyy hh:00:00"));
Приянка
источник
OP запросил DateTime в качестве возвращаемого объекта.
aj.toulan