Как обрезать миллисекунды от .NET DateTime


334

Я пытаюсь сравнить отметку времени из входящего запроса с сохраненным значением базы данных. Конечно, SQL Server сохраняет некоторую точность времени в миллисекундах, а при чтении в .NET DateTime он включает эти миллисекунды. Однако входящий запрос к системе не обеспечивает такой точности, поэтому мне нужно просто отбросить миллисекунды.

Я чувствую, что упускаю что-то очевидное, но я не нашел элегантного способа сделать это (C #).


(3-я попытка ...) Так как 20% ответов ( 1 , 2 , 3 ) описывают, как опустить или удалить компонент в миллисекундах из форматированного stringпредставления a DateTime, возможно, необходимо изменить, чтобы было ясно, что для «усечения» / «падение» миллисекунда означает «производит DateTimeзначение , где все компоненты даты / времени одинаковы , за исключением TimeOfDay.TotalMillisecondsявляется 0.» Люди не читают, конечно, но просто чтобы устранить двусмысленность.
Бекон

Ответы:


557

Следующее будет работать для DateTime, который имеет доли миллисекунд, а также сохраняет свойство Kind (Local, Utc или Undefined).

DateTime dateTime = ... anything ...
dateTime = new DateTime(
    dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), 
    dateTime.Kind
    );

или эквивалент и короче:

dateTime = dateTime.AddTicks( - (dateTime.Ticks % TimeSpan.TicksPerSecond));

Это можно обобщить в метод расширения:

public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
    if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException
    if (dateTime == DateTime.MinValue || dateTime == DateTime.MaxValue) return dateTime; // do not modify "guard" values
    return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}

который используется следующим образом:

dateTime = dateTime.Truncate(TimeSpan.FromMilliseconds(1)); // Truncate to whole ms
dateTime = dateTime.Truncate(TimeSpan.FromSeconds(1)); // Truncate to whole second
dateTime = dateTime.Truncate(TimeSpan.FromMinutes(1)); // Truncate to whole minute
...

Хотя я дам вам это, потому что вы технически правы, для людей, считывающих данные из SQL Server для сравнения с некоторыми распределенными данными (в моем случае, с веб-запросом), такое количество разрешения не требуется.
Джефф Путц

1
Ницца. Ясно, что кому-то нужно дать классу DateTime несколько методов расширения для округления до ближайшего значения, чтобы этот тип хорошего кодирования можно было повторно использовать.
chris.w.mclean

Это очень маловероятно, но разве этот подход не нарушается, когда тики = 0?
adotout

@adotout, метод Truncate, приведенный выше, вызовет исключение DivideByZeroException, если параметр timeSpan равен нулю. Это то, что вы имеете в виду под «подходом к разрыву, когда тики = 0»? Было бы лучше выдать ArgumentException, когда timeSpan равен нулю.
Джо

145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);

34
Ясно и просто, просто не забудьте добавить «, date.Kind» в конец конструктора, чтобы убедиться, что вы не потеряете важную часть информации.
JMcDaniel

9
Будьте осторожны с этим решением в чувствительном к производительности коде. Мое приложение тратило 12% процессорного времени в System.DateTime.GetDatePart .
Полковник Паника

3
Это просто, но медленнее, чем вопрос, помеченный как лучший ответ. Не то чтобы это может быть узким местом, но оно примерно в 7-8 раз медленнее.
Джонас

«Более медленные» операторы неверны, разница составляет от 50% до 100% в зависимости от времени выполнения; Чистая 4.7.2: 0.35μs против 0,62 мкс и ядра 3.1: 0,18 мкс против 0,12 мкс , что это микро-секунды (10 ^ -6 секунд)
juwens

62

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

Использование:

DateTime myDateSansMilliseconds = myDate.Truncate(TimeSpan.TicksPerSecond);
DateTime myDateSansSeconds = myDate.Truncate(TimeSpan.TicksPerMinute)

Класс:

public static class DateTimeUtils
{
    /// <summary>
    /// <para>Truncates a DateTime to a specified resolution.</para>
    /// <para>A convenient source for resolution is TimeSpan.TicksPerXXXX constants.</para>
    /// </summary>
    /// <param name="date">The DateTime object to truncate</param>
    /// <param name="resolution">e.g. to round to nearest second, TimeSpan.TicksPerSecond</param>
    /// <returns>Truncated DateTime</returns>
    public static DateTime Truncate(this DateTime date, long resolution)
    {
        return new DateTime(date.Ticks - (date.Ticks % resolution), date.Kind);
    }
}

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

2
На самом деле вам не нужны скобки вокруг% операндов.
ErikE

8
... но, по моему мнению, эти слова добавляют ясности.
Орион Элензил

28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);

70
-1: будет работать, только если значение DateTime не включает доли миллисекунды.
Джо

7
Использование этого метода привело к сбою некоторых из моих модульных тестов: Ожидаемое: 2010-05-05 15: 55: 49.000 Но было: 2010-05-05 15: 55: 49.000. Я предполагаю из-за того, что Джо упомянул о долях миллисекунды.
Сет Рено

6
Не работает для сериализации, например, 2010-12-08T11: 20: 03.000099 + 15: 00 является выходным сигналом, не полностью отсекает миллисекунды.
joedotnot

5
MillisecondСвойство дает целое число от 0 до 999 (включительно). Таким образом, если, скажем, время суток до операции было 23:48:49.1234567равно целому числу 123, а время суток после операции равно 23:48:49.0004567. Таким образом, оно не усечено до целого числа секунд.
Джеппе Стиг Нильсен

11

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

public enum DateTimeResolution
{
    Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}

public static DateTime Truncate(this DateTime self, DateTimeResolution resolution = DateTimeResolution.Second)
{
    switch (resolution)
    {
        case DateTimeResolution.Year:
            return new DateTime(self.Year, 1, 1, 0, 0, 0, 0, self.Kind);
        case DateTimeResolution.Month:
            return new DateTime(self.Year, self.Month, 1, 0, 0, 0, self.Kind);
        case DateTimeResolution.Day:
            return new DateTime(self.Year, self.Month, self.Day, 0, 0, 0, self.Kind);
        case DateTimeResolution.Hour:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerHour));
        case DateTimeResolution.Minute:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMinute));
        case DateTimeResolution.Second:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerSecond));
        case DateTimeResolution.Millisecond:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMillisecond));
        case DateTimeResolution.Tick:
            return self.AddTicks(0);
        default:
            throw new ArgumentException("unrecognized resolution", "resolution");
    }
}

9

Вместо того чтобы отбрасывать миллисекунды, а затем сравнивать, почему бы не сравнить разницу?

DateTime x; DateTime y;
bool areEqual = (x-y).TotalSeconds == 0;

или

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;

3
первый вариант не работает, потому что TotalSeconds является двойным; это также возвращает миллисекунды.
Йовен

1
Сравнение разницы не дает того же результата, что и усечение, тогда сравнение. Например, 5,900 и 6,100 с интервалом менее секунды, поэтому будет сравниваться с вашим методом. Но усеченные значения 5 и 6 разные. Что подходит, зависит от вашего требования.
Джо

7

Менее очевидно, но более чем в 2 раза быстрее:

// 10000000 runs

DateTime d = DateTime.Now;

// 484,375ms
d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);

3
Обратите внимание, что второй вариант, d.AddMilliseconds(-d.Millisecond)не обязательно перемещает DateTime точно на предыдущую, полную секунду. d.Ticks % TimeSpan.TicksPerMillisecondтики (где-то между 0 и 9 999) за пределами вашей секунды останутся.
Технеций

5

Чтобы округлить до второго:

dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerSecond)

Заменить TicksPerMinuteна округлить до минуты.


Если ваш код чувствителен к производительности, будьте осторожны с

new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second)

Мое приложение тратило 12% процессорного времени в System.DateTime.GetDatePart .


3

Способ для легкого чтения ...

//Remove milliseconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss", null);

И больше...

//Remove seconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm"), "yyyy-MM-dd HH:mm", null);

//Remove minutes
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH"), "yyyy-MM-dd HH", null);

//and go on...

4
Преобразование в строки и разбор - ужасная идея с точки зрения производительности.
Джефф Путц

2
@JeffPutz правда, но это , но просто. Подходит для автоматического теста, когда значение, вставленное и извлеченное из БД, теряет тики (моя точная ситуация). Однако этот ответ может быть даже проще, чем он есть, так как var now = DateTime.Parse(DateTime.Now.ToString())работает просто отлично.
Гримм Опинер

1
@GrimmTheOpiner - "... отлично работает", в основном, но не гарантировано. Что он делает: «Округляет DateTime с любой точностью,« Долгое время »настроено, как в настройках панели управления текущего пользователя». Что обычно, но не обязательно, секунды.
Джо

1
Как и его простота, производительность не является проблемой для автоматизированного тестирования.
Лян

1

Относительно ответа Диадистиса. Это сработало для меня, за исключением того, что мне пришлось использовать Floor для удаления дробной части деления перед умножением. Так,

d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

становится

d = new DateTime(Math.Floor(d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

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

Eppsy


1

2 Методы расширения для решений, упомянутых выше

    public static bool LiesAfterIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate > compareDate;
    }


    public static bool LiesAfterOrEqualsIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate >= compareDate;
    }

использование:

bool liesAfter = myObject.DateProperty.LiesAfterOrEqualsIgnoringMilliseconds(startDateTime, DateTimeKind.Utc);

1

Не самое быстрое решение, но простое и понятное:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)

0
DateID.Text = DateTime.Today.ToShortDateString();

Use ToShortDateString() //Date 2-02-2016
Use ToShortDateString() // Time 

И с помощью

ToLongDateString() // its show 19 February 2016.


-1. Я могу видеть , как этот вопрос может быть неправильно истолкован , как просят , чтобы произвести stringвместо DateTime, но это не включает компоненты времени с выхода полностью . (Это также делает Todayненужным доступ к собственности.)
BACON

0

Новый метод

String Date = DateTime.Today.ToString("dd-MMM-yyyy"); 

// определяем параметр передачи строки dd-mmm-yyyy return 24-feb-2016

Или показано в текстовом поле

txtDate.Text = DateTime.Today.ToString("dd-MMM-yyyy");

// положить на PageonLoad


-1. Я могу видеть , как этот вопрос может быть неправильно истолкован , как просят , чтобы произвести stringвместо DateTime, но это не включает компоненты времени с выхода полностью . (Это также делает Todayненужным доступ к собственности.)
BACON

0

В моем случае я стремился сохранить TimeSpan из инструмента datetimePicker без сохранения секунд и миллисекунд, и вот решение.

Сначала преобразуйте datetimePicker.value в нужный вам формат, который у меня равен «ЧЧ: мм», затем преобразуйте его обратно в TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;

Лучшим способом (более ясное намерение, избегающее форматирования и разборов из a string) для этого будет DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Либо вы можете использовать вариант метода расширения Джо, который работает со TimeSpanзначениями и использует TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));для усечения секунд.
Бекон

0

Это моя версия методов расширения, опубликованная здесь и в похожих вопросах. Это проверяет значение тиков в удобном для чтения виде и сохраняет DateTimeKind исходного экземпляра DateTime. (Это имеет незначительные, но важные побочные эффекты при хранении в базе данных, такой как MongoDB.)

Если истинной целью является усечение DateTime до заданного значения (т. Е. Часов / минут / секунд / мс), я рекомендую вместо этого реализовать этот метод расширения в своем коде. Это гарантирует, что вы можете усекать только с правильной точностью, и сохраняет важные метаданные DateTimeKind вашего исходного экземпляра:

public static DateTime Truncate(this DateTime dateTime, long ticks)
{
    bool isValid = ticks == TimeSpan.TicksPerDay 
        || ticks == TimeSpan.TicksPerHour 
        || ticks == TimeSpan.TicksPerMinute 
        || ticks == TimeSpan.TicksPerSecond 
        || ticks == TimeSpan.TicksPerMillisecond;

    // /programming/21704604/have-datetime-now-return-to-the-nearest-second
    return isValid 
        ? DateTime.SpecifyKind(
            new DateTime(
                dateTime.Ticks - (dateTime.Ticks % ticks)
            ),
            dateTime.Kind
        )
        : throw new ArgumentException("Invalid ticks value given. Only TimeSpan tick values are allowed.");
}

Затем вы можете использовать метод следующим образом:

DateTime dateTime = DateTime.UtcNow.Truncate(TimeSpan.TicksPerMillisecond);

dateTime.Kind => DateTimeKind.Utc

-1

Я знаю, что ответ довольно поздно, но лучший способ избавиться от миллисекунд

var currentDateTime = DateTime.Now.ToString("s");

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


1
Это не идеально. У вас есть строка, а не DateTime.
Джефф Путц
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.