Come posso rappresentare un valore solo tempo in .NET?


238

C'è un modo in cui uno può rappresentare un valore solo orario in .NET senza la data? Ad esempio, indicando l'orario di apertura di un negozio?

TimeSpanindica un intervallo, mentre voglio solo memorizzare un valore temporale. L'uso DateTimeper indicare ciò comporterebbe una novità DateTime(1,1,1,8,30,0)che non è davvero desiderabile.

Risposte:


144

Come altri hanno già detto, è possibile utilizzare a DateTimee ignorare la data oppure utilizzare a TimeSpan. Personalmente non sono appassionato di nessuna di queste soluzioni, poiché nessuno dei due tipi riflette il concetto che stai cercando di rappresentare - considero i tipi di data / ora in .NET come un po 'sparsi, che è una delle ragioni per cui ho iniziato Noda Time . In Noda Time, è possibile utilizzare il LocalTimetipo per rappresentare un'ora del giorno.

Una cosa da considerare: l'ora del giorno non è necessariamente il periodo di tempo trascorso dalla mezzanotte dello stesso giorno ...

(A parte un altro, se vuoi anche rappresentare un orario di chiusura di un negozio, potresti scoprire che vuoi rappresentare 24:00, cioè l'ora alla fine della giornata. La maggior parte delle API di data / ora - inclusa Noda Ora: non consentire che venga rappresentato come valore dell'ora del giorno.)


5
"[L] l'ora del giorno non è necessariamente il periodo di tempo trascorso dalla mezzanotte dello stesso giorno ..." L'ora legale è l'unica ragione? Solo curioso di sapere perché l'hai lasciato indefinito.
Jason,

14
@Jason: L'ora legale è l'unica ragione per la quale riesco a pensare alla svelta, ignorando i secondi da saltare come irrilevanti per la maggior parte delle applicazioni. Per lo più l'ho lasciato in quel modo per incoraggiare gli altri a pensare al perché. Suppongo sia una buona cosa che la gente pensi un po 'più profondamente alle date / orari di quanto non facciano attualmente :)
Jon Skeet,

LocalTime è esattamente ciò di cui ho bisogno per supportare le mie esigenze.
sduplooy,

1
@sduplooy: Vuoi aiutarci a portarlo da Joda Time allora? :)
Jon Skeet,

1
@Oakcool: Esattamente come ho detto il 18 maggio: Durationa Noda Time o TimeSpannel BCL. Probabilmente incapsulerei il "posto nel video + commento" come un tipo, e quindi avrei una matrice di quel tipo.
Jon Skeet,

164

Puoi usare la finestra temporale

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

[Modifica]
Considerando le altre risposte e la modifica alla domanda, utilizzerei comunque TimeSpan. Inutile creare una nuova struttura in cui una esistente dal framework è sufficiente.
Su queste righe finiresti per duplicare molti tipi di dati nativi.


19
Esattamente. DateTime utilizza TimeSpan esattamente per quello scopo. Doc per DateTime.TimeSpan Proprietà: "Un TimeSpan che rappresenta la frazione del giorno trascorso da mezzanotte".
Marcel Jackwerth,

4
TimeSpan indica un intervallo mentre il tempo di cui sto parlando non è un intervallo, ma un singolo punto fisso su un intervallo di date.
sduplooy,

3
Può anche essere usato come punto fisso e, come specificato nella domanda, è senza data. Dopo tutto, decidi come utilizzare questi tipi di dati per il tuo bene.
John G

9
@Giovanni G: Sebbene possa essere usato per rappresentare un punto fisso, concordo con l'OP - sovraccaricare l'uso di TimeSpanquesto è piuttosto brutto. È il migliore disponibile nel framework stesso, ma non è la stessa cosa che dire che è piacevole.
Jon Skeet,

5
A partire da .Net 3.5, MSDN documenta che "La struttura TimeSpan può essere utilizzata anche per rappresentare l'ora del giorno, ma solo se l'ora non è correlata a una data particolare.". In altre parole, questa è esattamente la soluzione alla domanda proposta.
Pharap,

34

Se questo svuotare Datedavvero bug, è possibile anche creare una semplice Timestruttura:

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

Oppure, perché preoccuparsi: se non è necessario eseguire alcun calcolo con tali informazioni, archiviarle come String.


2
Hmmm ... forse ... ma perché reinventare la ruota? Se la lingua ha già una classe / struttura (come fanno C # e VB.NET), seguitela. Ma capisco dove stai cercando di andare con la tua risposta.
Kris Krause,

1
In che modo questa struttura sarebbe diversa da TimeSpan, ciò la duplicherebbe in un certo modo.
John G,

4
Downvoting a causa dell'esistenza di TimeSpan, che già gestisce questo, e in modo significativamente migliore.
Noon Silk,

1
@silky, ho scritto questo dopo aver letto la prima risposta; OP ha detto sulla domanda che non voleva usare TimeSpan; Personalmente, sceglierei di utilizzare un semplice DateTime
Rubens Farias,

18
+1 Questo è meglio di un TimeSpan perché ha meno possibilità di interpretazione errata ... un TimeSpan è davvero pensato per essere usato come intervallo (vedi MSDN) quindi una proprietà come Days non ha alcun significato quando TimeSpan è usato come Time
Zaid Masud,

20

Dico usa un DateTime. Se non hai bisogno della parte della data, ignorala. Se hai bisogno di visualizzare solo il tempo per l'utente, invialo formattato per l'utente in questo modo:

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

Sembra che tutto il lavoro extra per creare una nuova classe o persino usare un TimeSpan non sia necessario.


Come mostreresti secondi e mili-secondi in questo metodo?
Mona Jalal,

5
@MonaJalal Millisecondi: microsecondi DateTime.Now.ToString("hh:mm:ss.fff");: DateTime.Now.ToString("hh:mm:ss.ffffff");nanosecondi (se DateTime ha anche tanta risoluzione): DateTime.Now.ToString("hh:mm:ss.fffffffff");secondo MSDN
Pharap

2
Quindi, i 5-10 minuti necessari per implementare un tipo appropriato per questo ti sembrano più lavoro che dover considerare nell'intera base di codice, per qualsiasi sviluppo futuro, che una proprietà DateTime potrebbe contenere solo un tempo e deve essere formattata come quello in quegli scenari, e potrebbe essere necessario ignorare la parte della data? Divertiti a eseguire il debug degli eventi in cui troverai "0001-01-01 10:00" nel tuo database, nelle comunicazioni esterne, ecc ....
MarioDS

10

Penso che la classe di Rubens sia una buona idea, quindi ho pensato di fare un campione immutabile della sua classe Time con una validazione di base.

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

La convalida che hai aggiunto è estremamente importante. Lo svantaggio principale della classe TimeSpan nella modellazione di un'ora è che l'ora del giorno può essere superiore a 24 ore.
shelbypereira,

Perché le ore, i minuti e i secondi usano int e non uint? Se non c'è motivo, penso che possano usare direttamente uint e questo evita il casting nel costruttore.
shelbypereira,

6

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

I tuoi metodi di aggiunta per minuti e secondi sono errati in quanto non tengono conto di valori superiori a 59.
Chibueze Opata

@Chibueze Opate: hai perfettamente ragione. Questo è stato solo veloce e sporco. Dovrei aggiungere altro lavoro a questo codice. Lo aggiornerò più tardi ... Grazie per il tuo suggerimento!
Jules il

5

Ecco una classe TimeOfDay completa.

Questo è eccessivo per casi semplici, ma se hai bisogno di funzionalità più avanzate come ho fatto io, questo può aiutare.

Può gestire casi angolari, alcuni calcoli matematici di base, confronti, interazione con DateTime, analisi, ecc.

Di seguito è riportato il codice sorgente per la classe TimeOfDay. Puoi vedere esempi di utilizzo e saperne di più qui :

Questa classe utilizza DateTime per la maggior parte dei suoi calcoli e confronti interni in modo da poter sfruttare tutta la conoscenza già incorporata in 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);
        }
    }
}

2

Se non si desidera utilizzare un DateTime o TimeSpan e si desidera semplicemente memorizzare l'ora del giorno, è possibile memorizzare i secondi dalla mezzanotte in un Int32 o (se non si desidera nemmeno i secondi) i minuti dalla mezzanotte si adatterebbe a un Int16. Sarebbe banale scrivere i pochi metodi richiesti per accedere a Ora, Minuti e Secondi da tale valore.

L'unico motivo che mi viene in mente di evitare DateTime / TimeSpan sarebbe se la dimensione della struttura è critica.

(Naturalmente, se usi uno schema semplice come quello sopra racchiuso in una classe, sarebbe anche banale sostituire l'archivio con un TimeSpan in futuro se improvvisamente ti rendessi conto che ti darebbe un vantaggio)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.