Jak mogę reprezentować wartość tylko czasu w .NET?

238

Czy istnieje sposób, w jaki można przedstawić wartość .NET bez daty bez daty? Na przykład, wskazując czas otwarcia sklepu?

TimeSpanwskazuje zakres, a ja chcę tylko zapisać wartość czasu. Użycie DateTimedo wskazania tego spowoduje powstanie nowego, DateTime(1,1,1,8,30,0)co nie jest tak naprawdę pożądane.

sduplooy
źródło

Odpowiedzi:

144

Jak mówili inni, to może użyć DateTimei ignorować datę, lub użyć TimeSpan. Osobiście nie podoba mi się żadne z tych rozwiązań, ponieważ żaden typ nie odzwierciedla koncepcji, którą próbujesz przedstawić - uważam typy dat / godzin w .NET za nieco rzadkie, co jest jednym z powodów, dla których zacząłem Czas Noda . W Noda Time możesz użyć tego LocalTimetypu, aby reprezentować porę dnia.

Jedną rzecz do rozważenia: pora dnia niekoniecznie musi być długością od północy tego samego dnia ...

(Poza tym, jeśli chcesz również reprezentować godzinę zamknięcia sklepu, możesz uznać, że chcesz reprezentować 24:00, tj. Godzinę na koniec dnia. Większość interfejsów API daty / godziny - w tym Noda Czas - nie pozwól, aby było to przedstawiane jako wartość pory dnia).

Jon Skeet
źródło
5
„Pora dnia niekoniecznie jest długością od północy tego samego dnia ...” Czy jedynym powodem jest czas letni? Ciekawe, dlaczego zostawiłeś to na czas nieokreślony.
jason
14
@Jason: Oszczędzanie światła dziennego to jedyny powód, dla którego mogę wymyślić odręcznie - ignorowanie sekund przestępnych jako nieistotne dla większości aplikacji. W większości zostawiłem to, aby zachęcić innych do zastanowienia się, dlaczego tak jest. Sądzę, że dobrze jest, aby ludzie zastanowili się nieco bardziej nad datami / godzinami niż obecnie :)
Jon Skeet
LocalTime jest dokładnie tym, czego potrzebuję, aby spełnić moje wymagania.
sduplooy
1
@sduplooy: Czy masz ochotę pomóc nam przenieść go z Joda Time? :)
Jon Skeet,
1
@Oakcool: Dokładnie tak, jak powiedziałem 18 maja: Durationw Noda Time lub TimeSpanw BCL. Prawdopodobnie hermetyzuję „miejsce w filmie + komentarz” jako typ, a następnie mam tablicę tego typu.
Jon Skeet
164

Możesz użyć przedziału czasu

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

[Edytuj]
Biorąc pod uwagę inne odpowiedzi i edycję pytania, nadal używałbym TimeSpan. Nie ma sensu tworzyć nowej struktury, w której wystarczy istniejąca struktura.
W tych liniach skończyłoby się duplikowaniem wielu rodzimych typów danych.

John G.
źródło
19
Dokładnie. DateTime używa TimeSpan właśnie w tym celu. Doc dla właściwości DateTime.TimeSpan: „Przedział czasu reprezentujący ułamek dnia, który upłynął od północy”.
Marcel Jackwerth,
4
TimeSpan wskazuje interwał, podczas gdy czas, o którym mówię, nie jest interwałem, ale pojedynczym stałym punktem w zakresie dat.
sduplooy
3
Może być używany jako punkt stały, a jak określono w pytaniu, nie ma daty. W końcu decydujesz, jak korzystać z tych typów danych w celu uzyskania korzyści.
John G
9
@John G: Chociaż można go użyć do przedstawienia stałego punktu, zgadzam się z OP - przeciążenie tego typu użytkowaniem TimeSpanjest nieco brzydkie. To najlepsze, co jest dostępne w ramach samego frameworka, ale to nie to samo, co mówienie, że jest przyjemne.
Jon Skeet,
5
Począwszy od .Net 3.5, MSDN dokumentuje, że „Struktura TimeSpan może być również używana do reprezentowania pory dnia, ale tylko wtedy, gdy czas nie jest związany z konkretną datą.”. Innymi słowy, jest to dokładnie rozwiązanie proponowanego pytania.
Pharap
34

Jeśli ta pustka Datenaprawdę Cię popsuła, możesz także stworzyć prostszą Timestrukturę:

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

Lub po co zawracać sobie głowę: jeśli nie musisz wykonywać żadnych obliczeń z tymi informacjami, po prostu zapisz je jako String.

Rubens Farias
źródło
2
Hmmm ... może ... ale po co wymyślać koło? Jeśli język ma już klasę / strukturę (co robią C # i VB.NET), to idź z nim. Ale rozumiem, gdzie próbujesz iść z odpowiedzią.
Kris Krause,
1
Czym różni się ta struktura od TimeSpan, to po prostu powiela to w pewien sposób.
John G
4
Głosowanie w dół z powodu istnienia TimeSpan, które już sobie z tym radzi, i to w znacznie lepszy sposób.
południe Silk
1
@silky, napisałem to po przeczytaniu pierwszej odpowiedzi; OP powiedział w pytaniu, że nie chce używać TimeSpan; Ja osobiście wybrałbym zwykły DateTime
Rubens Farias
18
+1 Jest to lepsze niż TimeSpan, ponieważ ma mniejsze możliwości błędnej interpretacji ... TimeSpan naprawdę ma być używany jako interwał (patrz MSDN), więc właściwość taka jak Days nie ma znaczenia, gdy TimeSpan jest używany jako Time
Zaid Masud
20

Mówię: użyj DateTime. Jeśli nie potrzebujesz części z datą, po prostu ją zignoruj. Jeśli chcesz wyświetlić użytkownikowi tylko godzinę, wyślij go sformatowanemu użytkownikowi w następujący sposób:

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

Wydaje się, że cała dodatkowa praca związana z tworzeniem nowej klasy lub nawet korzystaniem z TimeSpan jest niepotrzebna.

bugfixr
źródło
Jak pokazałbyś sekundy i mili-sekundy w tej metodzie?
Mona Jalal,
5
@MonaJalal Milisekundy: Mikrosekundy DateTime.Now.ToString("hh:mm:ss.fff");: DateTime.Now.ToString("hh:mm:ss.ffffff");Nanosekundy (jeśli DateTime ma nawet taką rozdzielczość): DateTime.Now.ToString("hh:mm:ss.fffffffff");Zgodnie z MSDN
Pharap
2
Zatem od 5 do 10 minut potrzebnych do wdrożenia odpowiedniego typu wydaje się to dla ciebie więcej pracy niż konieczność uwzględnienia w całej bazie kodu, w celu przyszłego rozwoju, że właściwość DateTime może zawierać tylko czas i musi zostać sformatowana tak w tych scenariuszach, a część daty może wymagać zignorowania? Miłej zabawy przy debugowaniu zdarzeń, w których w bazie danych, w komunikacji zewnętrznej itp. Znajdziesz „0001-01-01 10:00”…
MarioDS
10

Myślę, że klasa Rubensa jest dobrym pomysłem, dlatego postanowiłem zrobić niezmienną próbkę jego klasy Time z podstawową walidacją.

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
źródło
Walidacja, którą dodałeś, jest niezwykle ważna. Główną wadą klasy TimeSpan w modelowaniu czasu jest to, że pora dnia może wynosić ponad 24 godziny.
shelbypereira
Dlaczego godziny, minuty i sekundy używają int, a nie uint? Jeśli nie ma żadnego powodu, myślę, że mogą bezpośrednio użyć uinta, co pozwala uniknąć rzutowania w konstruktorze.
shelbypereira
6

Oprócz 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
źródło
Metody dodawania minut i sekund są nieprawidłowe, ponieważ nie uwzględniają wartości powyżej 59.
Chibueze Opata
@Chibueze Opate: masz całkowitą rację. To było po prostu szybkie i brudne. Powinienem włożyć więcej pracy w ten kod. Zaktualizuję go później ... Dzięki za podpowiedź!
Jules
5

Oto w pełni funkcjonalna klasa TimeOfDay.

Jest to przesada w przypadku prostych przypadków, ale jeśli potrzebujesz bardziej zaawansowanych funkcji, takich jak ja, może to pomóc.

Może obsługiwać przypadki narożne, podstawowe matematyki, porównania, interakcję z DateTime, parsowanie itp.

Poniżej znajduje się kod źródłowy dla klasy TimeOfDay. Możesz zobaczyć przykłady użycia i dowiedzieć się więcej tutaj :

Ta klasa używa DateTime do większości swoich wewnętrznych obliczeń i porównań, dzięki czemu możemy wykorzystać całą wiedzę już osadzoną w 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
źródło
2

Jeśli nie chcesz korzystać z funkcji DateTime lub TimeSpan, a po prostu chcesz zapisać porę dnia, możesz po prostu zapisać sekundy od północy w Int32 lub (jeśli nie chcesz sekund) minuty od północy zmieściłby się w Int16. Zapisanie kilku metod wymaganych do uzyskania dostępu do godziny, minuty i sekundy z takiej wartości byłoby trywialne.

Jedynym powodem, dla którego mogę pomyśleć o uniknięciu DateTime / TimeSpan, jest to, że rozmiar struktury jest krytyczny.

(Oczywiście, jeśli użyjesz prostego schematu, takiego jak powyższy, zawiniętego w klasę, w przypadku, gdyby nagle zdasz sobie sprawę, że to da ci przewagę, byłoby również banalne zastąpienie magazynu pamięcią TimeSpan)

Jason Williams
źródło