Wie kann ich in .NET einen Zeitwert darstellen?

238

Gibt es eine Möglichkeit, einen Zeitwert in .NET ohne Datum darzustellen? Zum Beispiel die Öffnungszeit eines Geschäfts angeben?

TimeSpangibt einen Bereich an, während ich nur einen Zeitwert speichern möchte. Die Verwendung DateTime, um dies anzuzeigen, würde zu neuem führen, DateTime(1,1,1,8,30,0)was nicht wirklich wünschenswert ist.

sduplooy
quelle

Antworten:

143

Wie andere gesagt haben, Sie können ein verwenden DateTimeund ignorieren Sie das Datum oder ein verwenden TimeSpan. Persönlich bin ich an keiner dieser Lösungen interessiert, da keiner der beiden Typen wirklich das Konzept widerspiegelt, das Sie darstellen möchten. Ich betrachte die Datums- / Zeittypen in .NET als etwas spärlich, was einer der Gründe ist, warum ich angefangen habe Noda Zeit . In Noda Time können Sie den LocalTimeTyp verwenden, um eine Tageszeit darzustellen.

Eine Sache zu beachten: Die Tageszeit ist nicht unbedingt die Zeitspanne seit Mitternacht am selben Tag ...

(Abgesehen davon, wenn Sie auch eine Schließzeit eines Shops darstellen möchten, möchten Sie möglicherweise 24:00 Uhr darstellen, dh die Uhrzeit am Ende des Tages. Die meisten APIs für Datum und Uhrzeit - einschließlich Noda Zeit - Lassen Sie nicht zu, dass dies als Tageszeitwert dargestellt wird.)

Jon Skeet
quelle
5
"Die Tageszeit ist nicht unbedingt die Zeitspanne seit Mitternacht am selben Tag ..." Ist die Sommerzeit der einzige Grund? Nur neugierig, warum du es unbestimmt gelassen hast.
Jason
14
@Jason: Sommerzeit ist der einzige Grund, den ich mir vorstellen kann - Schaltsekunden zu ignorieren, die für die meisten Anwendungen irrelevant sind. Ich habe es meistens so gelassen, um andere zu ermutigen, darüber nachzudenken, warum das so sein könnte. Ich denke, es ist eine gute Sache für die Leute, ein bisschen tiefer über Daten / Zeiten nachzudenken als derzeit :)
Jon Skeet
LocalTime ist genau das, was ich brauche, um meine Anforderungen zu erfüllen.
SDUPLOO
1
@sduplooy: Möchtest du uns dann helfen, es von Joda Time zu portieren? :)
Jon Skeet
1
@ Oakcool: Genau wie ich am 18. Mai sagte: Durationin Noda Time oder TimeSpanin der BCL. Ich würde wahrscheinlich den "Platz in Video + Kommentar" als Typ einkapseln und dann ein Array dieses Typs haben.
Jon Skeet
164

Sie können die Zeitspanne verwenden

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

[Bearbeiten] In
Anbetracht der anderen Antworten und der Bearbeitung der Frage würde ich immer noch TimeSpan verwenden. Es macht keinen Sinn, eine neue Struktur zu erstellen, bei der eine vorhandene aus dem Framework ausreicht.
In diesen Zeilen würden Sie am Ende viele native Datentypen duplizieren.

John G.
quelle
19
Genau. DateTime verwendet TimeSpan genau für diesen Zweck. Doc for DateTime.TimeSpan-Eigenschaft: "Ein TimeSpan, der den Bruchteil des Tages darstellt, der seit Mitternacht vergangen ist."
Marcel Jackwerth
4
TimeSpan gibt ein Intervall an, während die Zeit, über die ich spreche, kein Intervall ist, sondern ein einzelner fester Punkt über einen Bereich von Daten.
SDUPLOO
3
Es kann gut als Fixpunkt verwendet werden, und wie Sie in der Frage angegeben haben, ist es ohne Datum. Schließlich entscheiden Sie, wie Sie diese Datentypen für Ihren Nutzen verwenden möchten.
John G
9
@ John G: Obwohl es verwendet werden kann, um einen festen Punkt darzustellen, stimme ich dem OP zu - das Überladen der Verwendung von TimeSpanso etwas ist etwas hässlich. Es ist das Beste, was innerhalb des Frameworks selbst verfügbar ist, aber das ist nicht dasselbe wie zu sagen, dass es angenehm ist.
Jon Skeet
5
Ab .Net 3.5 dokumentiert MSDN, dass "die TimeSpan-Struktur auch zur Darstellung der Uhrzeit verwendet werden kann, jedoch nur, wenn die Uhrzeit nicht mit einem bestimmten Datum zusammenhängt." Mit anderen Worten, dies ist genau die Lösung für die vorgeschlagene Frage.
Pharap
34

Wenn Sie das Datewirklich stört, können Sie auch eine einfachere TimeStruktur erstellen :

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

Oder warum sich die Mühe machen: Wenn Sie mit diesen Informationen keine Berechnung durchführen müssen, speichern Sie sie einfach als String.

Rubens Farias
quelle
2
Hmmm ... vielleicht ... aber warum das Rad neu erfinden? Wenn die Sprache bereits eine Klasse / Struktur hat (was C # und VB.NET tun), dann machen Sie mit. Aber ich verstehe, wohin Sie mit Ihrer Antwort gehen wollen.
Kris Krause
1
Wie würde sich diese Struktur von TimeSpan unterscheiden, dies würde sie nur in gewisser Weise duplizieren.
John G
4
Downvoting Sie aufgrund der Existenz von TimeSpan, die dies bereits handhabt, und auf eine wesentlich bessere Weise.
Mittag Seide
1
@silky, ich schrieb dies, nachdem ich die erste Antwort gelesen hatte; OP sagte auf Frage, er wolle TimeSpan nicht verwenden; Ich persönlich würde mich dafür entscheiden, eine einfache DateTime
Rubens Farias
18
+1 Dies ist besser als ein Timespan , weil es weniger Möglichkeiten der Fehlinterpretation hat ... eine Zeitspanne wirklich als Intervall verwendet werden soll (siehe MSDN) so eine Eigenschaft wie Tage keine Bedeutung hat , wenn Timespan als Zeit verwendet wird
Zaid Masud
20

Ich sage, benutze eine DateTime. Wenn Sie den Datumsteil nicht benötigen, ignorieren Sie ihn einfach. Wenn Sie dem Benutzer nur die Uhrzeit anzeigen müssen, geben Sie sie wie folgt formatiert für den Benutzer aus:

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

Es scheint, als wäre die zusätzliche Arbeit, eine neue Klasse zu erstellen oder sogar einen TimeSpan zu verwenden, unnötig.

Bugfixr
quelle
Wie würden Sie bei dieser Methode Sekunden und Millisekunden anzeigen?
Mona Jalal
5
@MonaJalal Millisekunden: DateTime.Now.ToString("hh:mm:ss.fff");Mikrosekunden: DateTime.Now.ToString("hh:mm:ss.ffffff");Nanosekunden (wenn Datetime hat einmal so viel Auflösung): DateTime.Now.ToString("hh:mm:ss.fffffffff");Wie pro MSDN
Pharap
2
Die 5 bis 10 Minuten, die für die Implementierung eines geeigneten Typs erforderlich sind, scheinen Ihnen mehr Arbeit zu sein, als in der gesamten Codebasis für jede zukünftige Entwicklung berücksichtigen zu müssen, dass eine DateTime-Eigenschaft möglicherweise nur eine Zeit enthält und formatiert werden muss so in diesen Szenarien, und der Datumsteil muss möglicherweise ignoriert werden? Viel Spaß beim Debuggen der Vorkommen, bei denen Sie "0001-01-01 10:00" in Ihrer Datenbank, in der externen Kommunikation usw. finden.
MarioDS
10

Ich denke, Rubens 'Klasse ist eine gute Idee, also dachte man daran, eine unveränderliche Stichprobe seiner Zeitklasse mit grundlegender Validierung zu machen.

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
quelle
Die von Ihnen hinzugefügte Validierung ist äußerst wichtig. Der Hauptnachteil der TimeSpan-Klasse beim Modellieren einer Zeit besteht darin, dass die Tageszeit mehr als 24 Stunden betragen kann.
Shelbypereira
Warum verwenden die Stunden, Minuten und Sekunden int und nicht uint? Wenn es keinen Grund gibt, denke ich, können sie uint direkt verwenden, und dies vermeidet das Casting im Konstruktor.
Shelbypereira
6

Neben 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
quelle
Ihre Additionsmethoden für Minuten und Sekunden sind falsch, da sie keine Werte über 59 berücksichtigen.
Chibueze Opata
@Chibueze Opate: Sie haben völlig Recht. Das war nur schnell und dreckig. Ich sollte noch etwas Arbeit in diesen Code stecken. Ich werde es später aktualisieren ... Danke für Ihren Hinweis!
Jules
5

Hier ist eine TimeOfDay-Klasse mit vollem Funktionsumfang.

Dies ist in einfachen Fällen übertrieben, aber wenn Sie wie ich erweiterte Funktionen benötigen, kann dies hilfreich sein.

Es kann die Eckfälle, einige grundlegende Berechnungen, Vergleiche, die Interaktion mit DateTime, das Parsen usw. behandeln.

Unten finden Sie den Quellcode für die TimeOfDay-Klasse. Sie können Anwendungsbeispiele sehen und erfahren Sie mehr hier :

Diese Klasse verwendet DateTime für die meisten internen Berechnungen und Vergleiche, damit wir das gesamte bereits in DateTime eingebettete Wissen nutzen können.

// 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
quelle
2

Wenn Sie keine DateTime oder TimeSpan verwenden und nur die Uhrzeit speichern möchten, können Sie die Sekunden seit Mitternacht in einem Int32 oder (wenn Sie nicht einmal Sekunden möchten) die Minuten seit Mitternacht speichern würde in einen Int16 passen. Es wäre trivial, die wenigen Methoden zu schreiben, die erforderlich sind, um von einem solchen Wert aus auf Stunde, Minute und Sekunde zuzugreifen.

Der einzige Grund, warum ich DateTime / TimeSpan vermeiden kann, ist, wenn die Größe der Struktur kritisch ist.

(Wenn Sie ein einfaches Schema wie das oben beschriebene in einer Klasse verwenden, ist es natürlich auch trivial, den Speicher in Zukunft durch einen TimeSpan zu ersetzen, wenn Sie plötzlich feststellen, dass dies Ihnen einen Vorteil verschafft.)

Jason Williams
quelle