Cara memotong milidetik dari .NET DateTime

334

Saya mencoba membandingkan cap waktu dari permintaan masuk ke nilai tersimpan basis data. SQL Server tentu saja menjaga ketepatan milidetik pada waktu itu, dan ketika membaca .NET DateTime, itu termasuk milidetik itu. Permintaan yang masuk ke sistem, bagaimanapun, tidak menawarkan presisi itu, jadi saya hanya perlu menjatuhkan milidetik.

Saya merasa seperti kehilangan sesuatu yang jelas, tetapi saya belum menemukan cara yang elegan untuk melakukannya (C #).

Jeff Putz
sumber
(Percobaan ke-3 ...) Karena 20% jawaban ( 1 , 2 , 3 ) menjelaskan cara menghilangkan atau menghapus komponen milidetik dari stringrepresentasi terformat dari a DateTime, mungkin diperlukan pengeditan untuk menjelaskan bahwa untuk "memotong" / "drop" milidetik berarti "menghasilkan DateTimenilai di mana semua komponen tanggal / waktu yang sama kecuali TimeOfDay.TotalMillisecondsadalah 0." Orang-orang tidak membaca, tentu saja, tetapi hanya untuk menghilangkan ambiguitas.
BACON

Jawaban:

557

Berikut ini akan berfungsi untuk DateTime yang memiliki milidetik fraksional, dan juga mempertahankan properti Kind (Lokal, Utc atau Tidak Terdefinisi).

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

atau yang setara dan lebih pendek:

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

Ini dapat digeneralisasi menjadi metode ekstensi:

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));
}

yang digunakan sebagai berikut:

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
...
Joe
sumber
Sementara saya akan memberikan ini karena Anda secara teknis benar, bagi orang yang membaca data dari SQL Server untuk membandingkan dengan beberapa data yang didistribusikan (permintaan berbasis web, dalam kasus saya), jumlah resolusi ini tidak diperlukan.
Jeff Putz
1
Bagus. Jelas seseorang perlu memberikan kelas DateTime beberapa metode ekstensi untuk membulatkan ke terdekat apa pun sehingga jenis pengkodean yang baik ini akan digunakan kembali.
chris.w.mclean
Ini sangat tidak mungkin, tetapi tidakkah pendekatan ini pecah ketika ticks = 0?
adotout
@ adotout, metode Truncate di atas akan melempar DivideByZeroException jika parameter timeSpan adalah nol, apakah ini yang Anda maksud dengan "pendekatan break when ticks = 0"? Akan lebih baik untuk melempar ArgumentException ketika timeSpan adalah nol.
Joe
145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
benPearce
sumber
34
Jelas dan sederhana, hanya ingat untuk menambahkan ", date.Kind" ke akhir konstruktor untuk memastikan bahwa Anda tidak kehilangan informasi penting.
JMcDaniel
9
Berhati-hatilah dengan solusi ini dalam kode sensitif kinerja. Aplikasi saya menghabiskan 12% waktu CPU di System.DateTime.GetDatePart .
Kolonel Panic
3
Ini sederhana, tetapi lebih lambat dari pertanyaan yang ditandai sebagai jawaban terbaik. Bukan berarti ini bisa menjadi hambatan, tetapi kira-kira lebih lambat 7-8x.
Jonas
Pernyataan "jauh lebih lambat" tidak tepat benar, perbedaannya antara 50% dan sekitar 100% tergantung pada runtime; net 4.7.2: 0.35µs vs 0.62 µs dan core 3.1: 0.18 µs vs 0.12 µs itu mikro-detik (10 ^ -6 detik)
remaja
62

Berikut adalah metode ekstensi berdasarkan pada jawaban sebelumnya yang akan memungkinkan Anda memotong ke resolusi apa pun ...

Pemakaian:

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

Kelas:

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);
    }
}
Sky Sanders
sumber
1
Ini adalah solusi yang sangat fleksibel dan dapat digunakan kembali, yang ringkas dan ekspresif tanpa terlalu bertele-tele. Pilihan saya sebagai solusi terbaik.
Jaans
2
Anda sebenarnya tidak membutuhkan tanda kurung di sekitar% operan.
ErikE
8
.. tapi orangtua menambahkan kejelasan, menurut pendapat saya.
orion elenzil
28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);
chris.w.mclean
sumber
70
-1: Akan bekerja hanya jika nilai DateTime tidak termasuk fraksi satu milidetik.
Joe
7
Menggunakan metode ini menyebabkan beberapa pengujian unit saya gagal: Diharapkan: 2010-05-05 15: 55: 49.000 Tetapi sebelumnya: 2010-05-05 15: 55: 49.000. Saya menduga karena apa yang disebutkan Joe tentang pecahan satu milidetik.
Seth Reno
6
Tidak berfungsi untuk serialisasi, mis. 2010-12-08T11: 20: 03.000099 + 15: 00 adalah output, tidak sepenuhnya memotong milidetik.
joedotnot
5
The Millisecondproperti memberikan bilangan bulat antara 0 dan 999 (inklusif). Jadi jika waktu sehari sebelum operasi adalah, katakanlah 23:48:49.1234567, maka bilangan bulat itu akan menjadi 123, dan waktu hari setelah operasi adalah 23:48:49.0004567. Jadi itu belum terpotong ke sejumlah detik.
Jeppe Stig Nielsen
11

Terkadang Anda ingin memotong ke sesuatu berbasis kalender, seperti tahun atau bulan. Inilah metode ekstensi yang memungkinkan Anda memilih resolusi apa pun.

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");
    }
}
KingPong
sumber
9

Alih-alih menjatuhkan milidetik kemudian membandingkan, mengapa tidak membandingkan perbedaannya?

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

atau

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;
Bob
sumber
3
opsi pertama tidak berfungsi, karena TotalSeconds adalah ganda; itu juga mengembalikan milidetik.
Jowen
1
Membandingkan perbedaan tidak memberikan hasil yang sama dengan memotong lalu membandingkan. Misalnya 5.900 dan 6.100 berjarak kurang dari sedetik, jadi akan dibandingkan dengan metode Anda. Tetapi nilai terpotong 5 dan 6 berbeda. Yang sesuai tergantung pada kebutuhan Anda.
Joe
7

Kurang jelas tetapi lebih dari 2 kali lebih cepat:

// 10000000 runs

DateTime d = DateTime.Now;

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

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);
Diadistis
sumber
3
Perhatikan bahwa opsi kedua,, d.AddMilliseconds(-d.Millisecond)tidak harus memindahkan DateTime tepat ke sebelumnya, detik penuh. d.Ticks % TimeSpan.TicksPerMillisecondticks (suatu tempat antara 0 dan 9.999) di luar detik Anda akan tetap ada.
Technetium
5

Untuk membulatkan ke yang kedua:

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

Ganti dengan TicksPerMinutemembulatkan ke menit.


Jika kode Anda sensitif terhadap kinerja, berhati-hatilah

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

Aplikasi saya menghabiskan 12% waktu CPU di System.DateTime.GetDatePart .

Kolonel Panic
sumber
3

Cara untuk membaca mudah adalah ...

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

Dan lagi ...

//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...
Sergio Cabral
sumber
4
Mengubah ke string dan parsing adalah ide yang buruk dalam hal kinerja.
Jeff Putz
2
@JeffPutz benar, tetapi adalah tapi sederhana. Cocok untuk tes otomatis di mana nilai yang dimasukkan dan ditarik dari DB kehilangan kutu (situasi tepat saya). Namun jawaban ini bisa lebih sederhana dari itu, karena var now = DateTime.Parse(DateTime.Now.ToString())berfungsi dengan baik.
Grimm The Opiner
1
@ GrimmTheOpiner - "... berfungsi dengan baik", sebagian besar, tetapi tidak dijamin. Kegunaannya adalah: "Membulatkan DateTime ke ketepatan yang mana pun 'Long time' dikonfigurasi seperti pada preferensi Panel Kontrol pengguna saat ini". Yang biasanya, tetapi tidak harus, detik.
Joe
1
seperti kesederhanaannya, kinerja bukan masalah untuk situasi pengujian otomatis.
liang
1

Seputar tanggapan Diadistis. Ini berhasil bagi saya, kecuali saya harus menggunakan Lantai untuk menghapus bagian pecahan divisi sebelum perkalian. Begitu,

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

menjadi

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

Saya akan mengharapkan pembagian dua nilai Long menghasilkan Long, sehingga menghapus bagian desimal, tetapi mengatasinya sebagai Double yang meninggalkan nilai yang sama persis setelah perkalian.

Eppsy


sumber
1

2 Metode ekstensi untuk solusi yang disebutkan di atas

    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;
    }

pemakaian:

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

Bukan solusi tercepat tetapi sederhana dan mudah dipahami:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)
AlliterativeAlice
sumber
0
DateID.Text = DateTime.Today.ToShortDateString();

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

Dan Dengan Menggunakan

ToLongDateString() // its show 19 February 2016.

: P

Dhawal Shukal
sumber
-1. Saya bisa melihat bagaimana pertanyaannya mungkin disalahtafsirkan sebagai meminta untuk menghasilkan, stringbukan DateTime, tetapi ini menghilangkan komponen waktu dari output sepenuhnya . (Itu membuat mengakses Todayproperti tidak perlu juga.)
BACON
0

Metode Baru

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

// tentukan parameter pass String dd-mmm-yyyy return 24-feb-2016

Atau ditampilkan di kotak teks

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

// pakai PageonLoad

Dhawal Shukal
sumber
-1. Saya bisa melihat bagaimana pertanyaannya mungkin disalahtafsirkan sebagai meminta untuk menghasilkan, stringbukan DateTime, tetapi ini menghilangkan komponen waktu dari output sepenuhnya . (Itu membuat mengakses Todayproperti tidak perlu juga.)
BACON
0

Dalam kasus saya, saya bertujuan untuk menghemat TimeSpan dari alat datetimePicker tanpa menyimpan detik dan milidetik, dan inilah solusinya.

Pertama-tama, konversikan datetimePicker.value ke format yang Anda inginkan, yang milik saya adalah "HH: mm" lalu konversikan kembali ke TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;
Bell Pemikir
sumber
Cara yang lebih baik (maksud yang lebih jelas, hindari pemformatan dan parsing kembali dari a string) untuk melakukan ini adalah DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Atau Anda bisa menggunakan variasi metode ekstensi Joe yang beroperasi pada TimeSpannilai dan digunakan TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));untuk memotong detik.
BACON
0

Ini adalah versi saya dari metode ekstensi yang diposting di sini dan dalam pertanyaan serupa. Ini memvalidasi nilai kutu dengan cara yang mudah dibaca dan mempertahankan DateTimeKind dari instance DateTime asli. (Ini memiliki efek samping yang halus tetapi relevan ketika menyimpan ke database seperti MongoDB.)

Jika tujuan sebenarnya adalah memotong DateTime ke nilai yang ditentukan (mis. Jam / Menit / Detik / MS) Saya sarankan menerapkan metode ekstensi ini dalam kode Anda. Ini memastikan bahwa Anda hanya dapat memotong ke presisi yang valid dan menjaga metadata DateTimeKind penting dari instance asli Anda:

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.");
}

Maka Anda dapat menggunakan metode seperti ini:

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

dateTime.Kind => DateTimeKind.Utc
Kyle L.
sumber
-1

Saya tahu jawabannya cukup terlambat, tetapi cara terbaik untuk menghilangkan milidetik adalah

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

Coba cetak nilai variabel, itu akan menunjukkan waktu tanggal, tanpa milidetik.

Nisarg Shah
sumber
1
Ini tidak ideal. Anda punya string saat itu dan bukan DateTime.
Jeff Putz