Bagaimana cara saya mewakili nilai waktu saja dalam .NET?

238

Apakah ada cara yang dapat mewakili nilai hanya waktu di .NET tanpa tanggal? Misalnya, menunjukkan waktu pembukaan toko?

TimeSpanmenunjukkan rentang, sedangkan saya hanya ingin menyimpan nilai waktu. Menggunakan DateTimeuntuk menunjukkan ini akan menghasilkan yang baru DateTime(1,1,1,8,30,0)yang tidak benar-benar diinginkan.

sduplooy
sumber

Jawaban:

144

Seperti yang dikatakan orang lain, Anda dapat menggunakan DateTimedan mengabaikan tanggal, atau menggunakan a TimeSpan. Secara pribadi saya tidak tertarik pada salah satu dari solusi ini, karena tidak ada tipe yang benar-benar mencerminkan konsep yang Anda coba wakili - Saya menganggap tipe tanggal / waktu dalam. Waktu Noda . Dalam Waktu Noda, Anda dapat menggunakanLocalTime tipe untuk mewakili waktu dalam sehari.

Satu hal yang perlu dipertimbangkan: waktu hari belum tentu lamanya waktu sejak tengah malam di hari yang sama ...

(Selain itu, jika Anda juga ingin mewakili waktu penutupan toko, Anda mungkin ingin mewakili 24:00, yaitu waktu di akhir hari. Sebagian besar tanggal / waktu API - termasuk Noda Waktu - jangan izinkan itu direpresentasikan sebagai nilai waktu hari.)

Jon Skeet
sumber
5
"[T] dia waktu hari belum tentu lamanya waktu sejak tengah malam di hari yang sama ..." Apakah waktu musim panas satu-satunya alasan? Hanya ingin tahu mengapa Anda meninggalkannya tanpa batas.
jason
14
@Jason: Daylight saving adalah satu-satunya alasan yang bisa saya pikirkan begitu saja - mengabaikan detik kabisat sebagai tidak relevan untuk sebagian besar aplikasi. Sebagian besar saya membiarkannya seperti itu untuk mendorong orang lain berpikir mengapa itu mungkin terjadi. Saya rasa itu adalah hal yang baik bagi orang-orang untuk berpikir sedikit lebih dalam tentang tanggal / waktu daripada saat ini :)
Jon Skeet
LocalTime adalah persis apa yang saya butuhkan untuk mendukung kebutuhan saya.
sduplooy
1
@sduplooy: Ingin membantu kami porting dari Joda Time? :)
Jon Skeet
1
@Oakcool: Persis seperti yang saya katakan pada tanggal 18 Mei: Durationdi Noda Time, atau TimeSpandi BCL. Saya mungkin merangkum "tempat di video + komentar" sebagai tipe, dan kemudian memiliki array tipe itu.
Jon Skeet
164

Anda dapat menggunakan rentang waktu

TimeSpan timeSpan = new TimeSpan(2, 14, 18);
Console.WriteLine(timeSpan.ToString());     // Displays "02:14:18".

[Sunting]
Mempertimbangkan jawaban lain dan sunting pertanyaan, saya masih akan menggunakan TimeSpan. Tidak ada gunanya menciptakan struktur baru di mana yang sudah ada dari kerangka kerja sudah cukup.
Pada baris ini Anda akan menduplikasi banyak tipe data asli.

John G
sumber
19
Persis. DateTime menggunakan TimeSpan untuk tujuan itu. Doc untuk Properti DateTime.TimeSpan: "Sebuah TimeSpan yang mewakili sebagian kecil dari hari yang telah berlalu sejak tengah malam."
Marcel Jackwerth
4
TimeSpan menunjukkan interval sedangkan waktu yang saya bicarakan bukanlah interval, tetapi satu titik tetap pada rentang tanggal.
sduplooy
3
Ini dapat digunakan sebagai titik tetap juga, dan seperti yang Anda tentukan dalam pertanyaan, itu tanpa tanggal. Bagaimanapun Anda memutuskan bagaimana menggunakan tipe data ini untuk keuntungan Anda.
John G
9
@ John G: Meskipun dapat digunakan untuk mewakili titik tetap, saya setuju dengan OP - kelebihan penggunaan TimeSpanseperti ini agak jelek. Itu yang terbaik yang tersedia dalam kerangka itu sendiri, tetapi itu tidak sama dengan mengatakan itu menyenangkan.
Jon Skeet
5
Pada .Net 3.5, MSDN mendokumentasikan bahwa "Struktur TimeSpan juga dapat digunakan untuk mewakili waktu hari, tetapi hanya jika waktu tidak terkait dengan tanggal tertentu.". Dengan kata lain, ini adalah solusi tepat untuk pertanyaan yang diajukan.
Pharap
34

Jika itu Datebenar - benar mengganggu Anda, Anda juga dapat membuat Timestruktur yang lebih sederhana :

// more work is required to make this even close to production ready
class Time
{
    // TODO: don't forget to add validation
    public int Hours   { get; set; }
    public int Minutes { get; set; }
    public int Seconds { get; set; }

    public override string ToString()
    {  
        return String.Format(
            "{0:00}:{1:00}:{2:00}",
            this.Hours, this.Minutes, this.Seconds);
    }
}

Atau, mengapa harus repot: jika Anda tidak perlu melakukan perhitungan dengan informasi itu, simpan saja sebagai String.

Rubens Farias
sumber
2
Hmmm ... mungkin ... tapi mengapa menemukan kembali rodanya? Jika bahasa sudah memiliki kelas / struktur (yang C # dan VB.NET lakukan), maka ikuti saja. Tapi saya mengerti di mana Anda mencoba menjawabnya.
Kris Krause
1
Bagaimana struktur ini berbeda dari TimeSpan, ini hanya akan menduplikasinya.
John G
4
Menurunkan Anda karena keberadaan TimeSpan, yang sudah menangani ini, dan dengan cara yang jauh lebih baik.
Noon Silk
1
@ Silky, saya menulis ini setelah membaca jawaban pertama; OP mengatakan pada pertanyaan dia tidak ingin menggunakan TimeSpan; Saya, secara pribadi, akan memilih untuk menggunakan DateTime yang sederhana
Rubens Farias
18
+1 Ini lebih baik daripada TimeSpan karena memiliki lebih sedikit kemungkinan salah tafsir ... TimeSpan benar-benar dimaksudkan untuk digunakan sebagai interval (lihat MSDN) sehingga properti seperti Days tidak memiliki arti ketika TimeSpan digunakan sebagai Time
Zaid Masud
20

Saya katakan menggunakan DateTime. Jika Anda tidak membutuhkan bagian tanggal, abaikan saja. Jika Anda perlu menampilkan waktu kepada pengguna, output diformat ke pengguna seperti ini:

DateTime.Now.ToString("t");  // outputs 10:00 PM

Sepertinya semua pekerjaan ekstra untuk membuat kelas baru atau bahkan menggunakan TimeSpan tidak perlu.

perbaikan bug
sumber
Bagaimana Anda menampilkan detik dan mili detik dalam metode ini?
Mona Jalal
5
@MonaJalal Milliseconds: DateTime.Now.ToString("hh:mm:ss.fff");Microseconds: DateTime.Now.ToString("hh:mm:ss.ffffff");Nanoseconds (jika DateTime bahkan memiliki resolusi sebanyak itu): DateTime.Now.ToString("hh:mm:ss.fffffffff");Sesuai MSDN
Pharap
2
Jadi, 5 hingga 10 menit yang diperlukan untuk menerapkan tipe yang tepat untuk ini tampaknya lebih bermanfaat bagi Anda daripada harus mempertimbangkan seluruh basis kode, untuk pengembangan di masa mendatang, bahwa properti DateTime mungkin hanya berisi waktu saja, dan harus diformat seperti itu dalam skenario itu, dan bagian tanggal mungkin perlu diabaikan? Bersenang-senang men-debug kejadian di mana Anda akan menemukan "0001-01-01 10:00" di database Anda, dalam komunikasi eksternal, dll ....
MarioDS
10

Saya pikir kelas Rubens adalah ide yang bagus jadi saya pikir untuk membuat sampel kelas Time yang tidak dapat diubah dengan validasi dasar.

class Time
{
    public int Hours   { get; private set; }
    public int Minutes { get; private set; }
    public int Seconds { get; private set; }

    public Time(uint h, uint m, uint s)
    {
        if(h > 23 || m > 59 || s > 59)
        {
            throw new ArgumentException("Invalid time specified");
        }
        Hours = (int)h; Minutes = (int)m; Seconds = (int)s;
    }

    public Time(DateTime dt)
    {
        Hours = dt.Hour;
        Minutes = dt.Minute;
        Seconds = dt.Second;
    }

    public override string ToString()
    {  
        return String.Format(
            "{0:00}:{1:00}:{2:00}",
            this.Hours, this.Minutes, this.Seconds);
    }
}
Chibueze Opata
sumber
Validasi yang Anda tambahkan sangat penting. Kelemahan utama dari kelas TimeSpan dalam pemodelan waktu adalah bahwa waktu sehari bisa lebih dari 24 jam.
shelbypereira
Mengapa Jam, menit dan detik menggunakan int dan bukan uint? Jika tidak ada alasan saya pikir mereka dapat langsung menggunakan uint dan ini menghindari casting di konstruktor.
shelbypereira
6

Selain Chibueze Opata :

class Time
{
    public int Hours   { get; private set; }
    public int Minutes { get; private set; }
    public int Seconds { get; private set; }

    public Time(uint h, uint m, uint s)
    {
        if(h > 23 || m > 59 || s > 59)
        {
            throw new ArgumentException("Invalid time specified");
        }
        Hours = (int)h; Minutes = (int)m; Seconds = (int)s;
    }

    public Time(DateTime dt)
    {
        Hours = dt.Hour;
        Minutes = dt.Minute;
        Seconds = dt.Second;
    }

    public override string ToString()
    {  
        return String.Format(
            "{0:00}:{1:00}:{2:00}",
            this.Hours, this.Minutes, this.Seconds);
    }

    public void AddHours(uint h)
    {
        this.Hours += (int)h;
    }

    public void AddMinutes(uint m)
    {
        this.Minutes += (int)m;
        while(this.Minutes > 59)
            this.Minutes -= 60;
            this.AddHours(1);
    }

    public void AddSeconds(uint s)
    {
        this.Seconds += (int)s;
        while(this.Seconds > 59)
            this.Seconds -= 60;
            this.AddMinutes(1);
    }
}
Jules
sumber
Metode add untuk menit dan detik Anda salah karena tidak memperhitungkan nilai di atas 59.
Chibueze Opata
@Hubungi Opate: Anda sepenuhnya benar. Ini cepat dan kotor. Saya harus lebih banyak bekerja dalam kode ini. Saya akan memperbaruinya nanti ... Terima kasih atas petunjuk Anda!
Jules
5

Inilah kelas TimeOfDay berfitur lengkap.

Ini berlebihan untuk kasus-kasus sederhana, tetapi jika Anda membutuhkan fungsionalitas yang lebih maju seperti yang saya lakukan, ini dapat membantu.

Ini dapat menangani kasus sudut, beberapa matematika dasar, perbandingan, interaksi dengan DateTime, penguraian, dll.

Di bawah ini adalah kode sumber untuk kelas TimeOfDay. Anda dapat melihat contoh penggunaan dan mempelajari lebih lanjut di sini :

Kelas ini menggunakan DateTime untuk sebagian besar perhitungan dan perbandingan internal sehingga kami dapat memanfaatkan semua pengetahuan yang sudah tertanam dalam DateTime.

// Author: Steve Lautenschlager, CambiaResearch.com
// License: MIT

using System;
using System.Text.RegularExpressions;

namespace Cambia
{
    public class TimeOfDay
    {
        private const int MINUTES_PER_DAY = 60 * 24;
        private const int SECONDS_PER_DAY = SECONDS_PER_HOUR * 24;
        private const int SECONDS_PER_HOUR = 3600;
        private static Regex _TodRegex = new Regex(@"\d?\d:\d\d:\d\d|\d?\d:\d\d");

        public TimeOfDay()
        {
            Init(0, 0, 0);
        }
        public TimeOfDay(int hour, int minute, int second = 0)
        {
            Init(hour, minute, second);
        }
        public TimeOfDay(int hhmmss)
        {
            Init(hhmmss);
        }
        public TimeOfDay(DateTime dt)
        {
            Init(dt);
        }
        public TimeOfDay(TimeOfDay td)
        {
            Init(td.Hour, td.Minute, td.Second);
        }

        public int HHMMSS
        {
            get
            {
                return Hour * 10000 + Minute * 100 + Second;
            }
        }
        public int Hour { get; private set; }
        public int Minute { get; private set; }
        public int Second { get; private set; }
        public double TotalDays
        {
            get
            {
                return TotalSeconds / (24d * SECONDS_PER_HOUR);
            }
        }
        public double TotalHours
        {
            get
            {
                return TotalSeconds / (1d * SECONDS_PER_HOUR);
            }
        }
        public double TotalMinutes
        {
            get
            {
                return TotalSeconds / 60d;
            }
        }
        public int TotalSeconds
        {
            get
            {
                return Hour * 3600 + Minute * 60 + Second;
            }
        }
        public bool Equals(TimeOfDay other)
        {
            if (other == null) { return false; }
            return TotalSeconds == other.TotalSeconds;
        }
        public override bool Equals(object obj)
        {
            if (obj == null) { return false; }
            TimeOfDay td = obj as TimeOfDay;
            if (td == null) { return false; }
            else { return Equals(td); }
        }
        public override int GetHashCode()
        {
            return TotalSeconds;
        }
        public DateTime ToDateTime(DateTime dt)
        {
            return new DateTime(dt.Year, dt.Month, dt.Day, Hour, Minute, Second);
        }
        public override string ToString()
        {
            return ToString("HH:mm:ss");
        }
        public string ToString(string format)
        {
            DateTime now = DateTime.Now;
            DateTime dt = new DateTime(now.Year, now.Month, now.Day, Hour, Minute, Second);
            return dt.ToString(format);
        }
        public TimeSpan ToTimeSpan()
        {
            return new TimeSpan(Hour, Minute, Second);
        }
        public DateTime ToToday()
        {
            var now = DateTime.Now;
            return new DateTime(now.Year, now.Month, now.Day, Hour, Minute, Second);
        }

        #region -- Static --
        public static TimeOfDay Midnight { get { return new TimeOfDay(0, 0, 0); } }
        public static TimeOfDay Noon { get { return new TimeOfDay(12, 0, 0); } }
        public static TimeOfDay operator -(TimeOfDay t1, TimeOfDay t2)
        {
            DateTime now = DateTime.Now;
            DateTime dt1 = new DateTime(now.Year, now.Month, now.Day, t1.Hour, t1.Minute, t1.Second);
            TimeSpan ts = new TimeSpan(t2.Hour, t2.Minute, t2.Second);
            DateTime dt2 = dt1 - ts;
            return new TimeOfDay(dt2);
        }
        public static bool operator !=(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else
            {
                return t1.TotalSeconds != t2.TotalSeconds;
            }
        }
        public static bool operator !=(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 != dt2;
        }
        public static bool operator !=(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 != dt2;
        }
        public static TimeOfDay operator +(TimeOfDay t1, TimeOfDay t2)
        {
            DateTime now = DateTime.Now;
            DateTime dt1 = new DateTime(now.Year, now.Month, now.Day, t1.Hour, t1.Minute, t1.Second);
            TimeSpan ts = new TimeSpan(t2.Hour, t2.Minute, t2.Second);
            DateTime dt2 = dt1 + ts;
            return new TimeOfDay(dt2);
        }
        public static bool operator <(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else
            {
                return t1.TotalSeconds < t2.TotalSeconds;
            }
        }
        public static bool operator <(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 < dt2;
        }
        public static bool operator <(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 < dt2;
        }
        public static bool operator <=(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else
            {
                if (t1 == t2) { return true; }
                return t1.TotalSeconds <= t2.TotalSeconds;
            }
        }
        public static bool operator <=(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 <= dt2;
        }
        public static bool operator <=(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 <= dt2;
        }
        public static bool operator ==(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else { return t1.Equals(t2); }
        }
        public static bool operator ==(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 == dt2;
        }
        public static bool operator ==(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 == dt2;
        }
        public static bool operator >(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else
            {
                return t1.TotalSeconds > t2.TotalSeconds;
            }
        }
        public static bool operator >(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 > dt2;
        }
        public static bool operator >(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 > dt2;
        }
        public static bool operator >=(TimeOfDay t1, TimeOfDay t2)
        {
            if (ReferenceEquals(t1, t2)) { return true; }
            else if (ReferenceEquals(t1, null)) { return true; }
            else
            {
                return t1.TotalSeconds >= t2.TotalSeconds;
            }
        }
        public static bool operator >=(TimeOfDay t1, DateTime dt2)
        {
            if (ReferenceEquals(t1, null)) { return false; }
            DateTime dt1 = new DateTime(dt2.Year, dt2.Month, dt2.Day, t1.Hour, t1.Minute, t1.Second);
            return dt1 >= dt2;
        }
        public static bool operator >=(DateTime dt1, TimeOfDay t2)
        {
            if (ReferenceEquals(t2, null)) { return false; }
            DateTime dt2 = new DateTime(dt1.Year, dt1.Month, dt1.Day, t2.Hour, t2.Minute, t2.Second);
            return dt1 >= dt2;
        }
        /// <summary>
        /// Input examples:
        /// 14:21:17            (2pm 21min 17sec)
        /// 02:15               (2am 15min 0sec)
        /// 2:15                (2am 15min 0sec)
        /// 2/1/2017 14:21      (2pm 21min 0sec)
        /// TimeOfDay=15:13:12  (3pm 13min 12sec)
        /// </summary>
        public static TimeOfDay Parse(string s)
        {
            // We will parse any section of the text that matches this
            // pattern: dd:dd or dd:dd:dd where the first doublet can
            // be one or two digits for the hour.  But minute and second
            // must be two digits.

            Match m = _TodRegex.Match(s);
            string text = m.Value;
            string[] fields = text.Split(':');
            if (fields.Length < 2) { throw new ArgumentException("No valid time of day pattern found in input text"); }
            int hour = Convert.ToInt32(fields[0]);
            int min = Convert.ToInt32(fields[1]);
            int sec = fields.Length > 2 ? Convert.ToInt32(fields[2]) : 0;

            return new TimeOfDay(hour, min, sec);
        }
        #endregion

        private void Init(int hour, int minute, int second)
        {
            if (hour < 0 || hour > 23) { throw new ArgumentException("Invalid hour, must be from 0 to 23."); }
            if (minute < 0 || minute > 59) { throw new ArgumentException("Invalid minute, must be from 0 to 59."); }
            if (second < 0 || second > 59) { throw new ArgumentException("Invalid second, must be from 0 to 59."); }
            Hour = hour;
            Minute = minute;
            Second = second;
        }
        private void Init(int hhmmss)
        {
            int hour = hhmmss / 10000;
            int min = (hhmmss - hour * 10000) / 100;
            int sec = (hhmmss - hour * 10000 - min * 100);
            Init(hour, min, sec);
        }
        private void Init(DateTime dt)
        {
            Init(dt.Hour, dt.Minute, dt.Second);
        }
    }
}
Steve Lautenschlager
sumber
2

Jika Anda tidak ingin menggunakan DateTime atau TimeSpan, dan hanya ingin menyimpan waktu hari, Anda bisa menyimpan detik sejak tengah malam di Int32, atau (jika Anda bahkan tidak ingin detik) menit sejak tengah malam akan masuk ke dalam Int16. Akan sepele untuk menulis beberapa metode yang diperlukan untuk mengakses Jam, Menit dan Kedua dari nilai tersebut.

Satu-satunya alasan saya dapat memikirkan untuk menghindari DateTime / TimeSpan adalah jika ukuran struktur sangat penting.

(Tentu saja, jika Anda menggunakan skema sederhana seperti di atas yang dibungkus dalam kelas, maka itu juga akan sepele untuk mengganti penyimpanan dengan TimeSpan di masa depan jika Anda tiba-tiba menyadari bahwa itu akan memberi Anda keuntungan)

Jason Williams
sumber