Як відрізати мілісекунд від .NET DateTime


334

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

Я відчуваю, що пропускаю щось очевидне, але не знайшов елегантного способу зробити це (C #).


(3-а спроба ...) Оскільки 20% відповідей ( 1 , 2 , 3 ) описують, як опустити або вилучити компонент мілісекунд із форматованого stringподання а DateTime, можливо, потрібна редакція, щоб зрозуміти, що "усікати" / «падіння» мілісекунда означає «виробляє DateTimeзначення , де всі компоненти дати / часу однакові , за винятком TimeOfDay.TotalMillisecondsє 0.» Люди, звичайно, не читають, а просто для усунення будь-якої неясності.
BACON

Відповіді:


556

Далі буде працювати для 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"? Було б краще кинути аргументException, коли 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 мкс проти 0,62 мкс та ядро 3,1: 0,18 мкс проти 0,12 мкс , це мікросекунди (10 ^ -6 секунд)
ювен

61

Ось метод розширення, заснований на попередній відповіді, який дозволить урізати будь-яку резолюцію ...

Використання:

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
Це дійсно гнучке та багаторазове рішення, яке є стислим та виразним, не надто багатослівним. Мій голос як найкраще рішення.
Яанс

2
Насправді не потрібні дужки навколо операндів%.
ЕрікЕ

8
.. але парен додає ясності, на мій погляд.
orion elenzil

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. Таким чином, вона не обрізається цілу кількість секунд.
Jeppe Stig Nielsen

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())працює чудово.
Grimm The Opiner

1
@GrimmTheOpiner - "... працює просто чудово", в основному, але не гарантується. Що це робить: "Обводить DateTime з будь-якою точністю" Long time "налаштовано, як у налаштуваннях поточної панелі керування користувача". Що зазвичай, але не обов’язково, секунди.
Джо

1
як і його простота, продуктивність не є проблемою для автоматизованого тестування ситуації.
лян

1

Щодо реакції Діадістіса. Це спрацювало для мене, за винятком того, що мені довелося використовувати Floor для видалення дробової частини ділення перед множенням. Тому,

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

стає

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

Я б очікував, що поділ двох значень Long призведе до Довгого, тим самим видаляючи десяткову частину, але це розв'язує його як подвійне, залишаючи абсолютно те саме значення після множення.

Епіпсія


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.

: P


-1. Я можу бачити , як це питання може бути неправильно витлумачений , як просять , щоб зробити stringзамість DateTime, але це не включає компоненти часу з виходу повністю . (Це також робить доступ до Todayвласності непотрібним.)
BACON

0

Новий метод

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

// визначити параметр String pass 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 у потрібний формат, який у мене "HH: mm", а потім перетворите його назад у TimeSpan.

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

Кращим способом (більш чіткий намір, уникнути форматування та розбору назад з а string) це було б. DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Або ви можете використати варіацію методу розширення Джо, який працює на TimeSpanзначеннях і використовується TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));для скорочення секунд.
BACON

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.