Как обрезать миллисекунды от .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.