Есть ли в C # лучший способ округлить DateTime до ближайших 5 секунд?

Я хочу округлить DateTime до ближайших 5 секунд. Я так делаю сейчас, но мне было интересно, есть ли способ лучше или более сжатый?

DateTime now = DateTime.Now;
int second = 0;

// round to nearest 5 second mark
if (now.Second % 5 > 2.5)
{
    // round up
    second = now.Second + (5 - (now.Second % 5));
}
else
{
    // round down
    second = now.Second - (now.Second % 5);
}

DateTime rounded = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, second);

Обратите внимание, что я нашел эти два предыдущих вопроса, однако они усекают, а не округляют время.


person Damovisa    schedule 20.04.2009    source источник


Ответы (7)


Счетчик тиков DateTime представляет интервалы в 100 наносекунд, поэтому вы можете округлить до ближайших 5 секунд, округлив до ближайшего интервала в 50000000 тиков следующим образом:

  DateTime now = DateTime.Now;
  DateTime rounded = new DateTime(((now.Ticks + 25000000) / 50000000) * 50000000);

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

person JayMcClellan    schedule 20.04.2009
comment
Это хорошо работает, потому что 59 секунд, округленных до ближайшего 5, дадут 60, которые вы не можете передать в качестве параметра «секунды» конструктору DateTime. Таким образом вы избежите этой ловушки. - person Matt Hamilton; 20.04.2009
comment
Одна потенциальная ловушка, если критиковать мой собственный ответ, заключается в том, что я не уверен, как DateTime учитывает дополнительные секунды. Счетчик тиков отсчитывается с 12:00:00 до полуночи 1 января 0001. Таким образом, в зависимости от количества дополнительных секунд с тех пор и от того, учитывает ли их DateTime, вы можете обнаружить, что результирующее значение секунд не кратно 5. - person JayMcClellan; 20.04.2009
comment
Вау, теперь это подробно ... Думаю, я достаточно счастлив, чтобы игнорировать потенциальную ошибку примерно каждые 18 месяцев. - person Damovisa; 20.04.2009
comment
вы можете сделать его более читабельным, используя TimeSpan.TicksPerSecond. - person Erich Mirabal; 20.04.2009
comment
Или используйте 24999999, чтобы получить поведение в вопросе, где кратные 2,5 округляют вниз. В остальном хорошее решение. Есть ли какой-нибудь язык, который позволяет использовать запятые в числах, чтобы сделать их более читаемыми? Мне бы очень хотелось увидеть эту особенность, потому что числа вроде 50000000 меня до чертиков раздражают. 50 000 000 было бы намного лучше. - person paxdiablo; 20.04.2009
comment
@Pax - вы правы, вопрос будет округлен в сторону уменьшения от 2,5 до 3 секунд. Это не совсем так, как мне нужно, меня просто торопили. - person Damovisa; 20.04.2009
comment
Джей Макклеллан: Это не имеет значения. .NET (и Windows) не могут обрабатывать дополнительные секунды. - person porges; 27.09.2010
comment
Вы можете улучшить читаемость, используя TimeSpan.TicksPerSecond и Math.Round - person Stefan Steinegger; 11.04.2012

(Извините за воскрешение; я понимаю, что это старый вопрос, на который дан ответ - просто добавляю дополнительный код для Google.)

Я начал с ответ JayMcClellan, но затем я хотел, чтобы он был более общим, с округлением до произвольных интервалов (а не только 5 секунд). В итоге я оставил метод Джея для метода, который использует Math.Round на тиках, и поместил его в метод расширения, который может принимать произвольные интервалы, а также предлагает возможность изменения логики округления (округление банкира по сравнению с нулевым). Я публикую здесь на случай, если это будет полезно и для кого-то другого:

    public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval, MidpointRounding roundingType) {
        return new TimeSpan(
            Convert.ToInt64(Math.Round(
                time.Ticks / (decimal)roundingInterval.Ticks,
                roundingType
            )) * roundingInterval.Ticks
        );
    }

    public static TimeSpan Round(this TimeSpan time, TimeSpan roundingInterval) {
        return Round(time, roundingInterval, MidpointRounding.ToEven);
    }

    public static DateTime Round(this DateTime datetime, TimeSpan roundingInterval) {
        return new DateTime((datetime - DateTime.MinValue).Round(roundingInterval).Ticks);
    }

Он не получит никаких наград за чистую эффективность, но я считаю, что он легко читается и интуитивно понятен в использовании. Пример использования:

new DateTime(2010, 11, 4, 10, 28, 27).Round(TimeSpan.FromMinutes(1)); // rounds to 2010.11.04 10:28:00
new DateTime(2010, 11, 4, 13, 28, 27).Round(TimeSpan.FromDays(1)); // rounds to 2010.11.05 00:00
new TimeSpan(0, 2, 26).Round(TimeSpan.FromSeconds(5)); // rounds to 00:02:25
new TimeSpan(3, 34, 0).Round(TimeSpan.FromMinutes(37); // rounds to 03:42:00...for all your round-to-37-minute needs
person Matt Winckler    schedule 05.11.2010
comment
Это хороший код для округления до ближайшего DateTime, но мне также нужна возможность округлять вверх до кратного roundingInterval. Я попытался изменить перегрузку DateTime, чтобы она также принимала MidpointRounding, а затем передавала ее другой перегрузке. Но это не добавило желаемой способности. - person HappyNomad; 26.11.2013
comment
@HappyNomad MidpointRounding вступает в игру только тогда, когда значение находится в фактической средней точке. Если вы всегда хотите округлить, вам нужно будет добавить перегрузку или изменить первую функцию, чтобы использовать Math.Ceiling вместо Math.Round, и полностью игнорировать roundingType. - person Matt Winckler; 26.11.2013
comment
Предложите сохранить такой же Kind на новом DateTime, который вы создаете. - person Nigel Touch; 30.11.2013

Как вы упомянули, усечь довольно легко. Итак, просто добавьте 2,5 секунды, а затем усеките.

person Sophie Alpert    schedule 20.04.2009
comment
Если я добавлю 2,5 секунды, усечу до ближайших 5 секунд и вычтю 2,5 секунды, я получу 2,5 секунды, 7,5 секунды, 12,5 секунды и т. Д. - person Damovisa; 20.04.2009

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

static int Round(int n, int r)
{
    if ((n % r) <= r / 2)
    {
        return n - (n % r); 
    }
    return n + (r - (n % r));
}

Кроме того,% возвращает int, поэтому сравнение его с 2,5 кажется мне немного странным, хотя это и правильно. Я бы использовал> = 3.

person RossFabricant    schedule 20.04.2009
comment
Да, я знаю, что вы имеете в виду, сравнивая его с 2.5 - это было немного неудобно. Как вы говорите, это правильно, и это проясняет, в чем заключается намерение. 2,5 - это явно половина от 5, а 3, похоже, не подходят. - person Damovisa; 20.04.2009
comment
Я предпочитаю округлять целые числа следующим образом: ((n + (r>>1)) / r) * r (округлять средние точки вверх) или ((n + r>>1 - 1) / r) * r (округлять средние точки вниз). Если я знаю, что r нечетно, я просто использую первый, потому что они одинаково работают для нечетных r. Этот подход использует только одно деление (против 3) и никакого ветвления по сравнению с вашей функцией. - person Roman Starkov; 23.03.2010

Как насчет этого (объединение нескольких ответов вместе)? Я думаю, что он хорошо передает смысл и должен элегантно обрабатывать крайние случаи (округление до следующей минуты) благодаря AddSeconds.

// truncate to multiple of 5
int second = 5 * (int) (now.Second / 5);
DateTime dt = new DateTime(..., second);

// round-up if necessary
if (now.Second % 5 > 2.5)
{
    dt = dt.AddSeconds(5);
}

Подход Ticks, показанный Джеем, более краток, но может быть немного менее читаемым. Если вы используете такой подход, по крайней мере, обратитесь к TimeSpan.TicksPerSecond.

person Erich Mirabal    schedule 20.04.2009
comment
-1: не работает, если исходное время содержит доли секунды. - person Joe; 20.04.2009
comment
Ты прав. Я откатился к предыдущей редакции, которая была более понятной в этом случае. - person Erich Mirabal; 20.04.2009
comment
Спасибо, что держал меня честным :) Сегодня утром я проснулся от этого -1 и подумал: Ага! Глупая ошибка. Не думайте, что вы можете оптимизировать, пока спите! - person Erich Mirabal; 20.04.2009
comment
Из любопытства, должны ли ответы, приводящие к исключению (из-за неправильного обращения со случаем, когда second == 60), также не отклоняться? - person Erich Mirabal; 20.04.2009

Я не мог распознать разницу между C # и куском мыла (ну, я не мог, когда изначально писал этот ответ, с тех пор все немного изменилось), но, если вы ищете более < em> краткое решение, я бы просто поместил все это в функцию - в вашем коде мало что будет более кратким, чем простой вызов указанной функции:

DateTime rounded = roundTo5Secs (DateTime.Now);

Затем вы можете поместить в функцию все, что хотите, и просто задокументировать, как она работает, например (при условии, что это все целочисленные операции):

secBase = now.Second / 5;
secExtra = now.Second % 5;
if (secExtra > 2) {
    return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
        secBase + 5);
}
return new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute,
    secBase);

Вам также могут потребоваться дополнительные проверки, если secBase переходит на 60 (если только объекты C # DateTime не достаточно умны, чтобы увеличить значение минуты (и часа, если значение минуты доходит до 60, и т. Д.).

person paxdiablo    schedule 20.04.2009
comment
Да, справедливо. Я сделаю это - я просто не уточнил в вопросе. - person Damovisa; 20.04.2009
comment
Время купаться для вас должно быть трудным ;-) - person Gordon Thompson; 20.06.2013
comment
Хороший, @Gordon, я знаю немного больше о C # в настоящее время, но купание довольно сложно. Хотя это больше связано с маленькими детьми, а не с отсутствием координации с моей стороны :-) - person paxdiablo; 20.06.2013

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

2, 4, 6, 8, 10 ‹- без проблем

Если вы «распределяете» время по интервалам и если дрожание невелико, усечение будет намного более управляемым.

Если вы можете пропустить миллисекунды и округлить на отметке 500 мс, вы сможете до нечетных секунд, а также уменьшить эффект джиттера или полностью устранить его.

person Jim K.    schedule 22.01.2013