Як відрізати мілісекунд від .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.