Comment représenter une valeur de temps uniquement dans .NET?

238

Existe-t-il un moyen de représenter une valeur de temps uniquement dans .NET sans la date? Par exemple, en indiquant l'heure d'ouverture d'une boutique?

TimeSpanindique une plage, alors que je veux seulement stocker une valeur de temps. En utilisantDateTime pour indiquer cela entraînerait une nouvelle DateTime(1,1,1,8,30,0)qui n'est pas vraiment souhaitable.

sduplooy
la source

Réponses:

144

Comme d'autres l'ont dit, vous pouvez utiliser un DateTimeet ignorer la date, ou utiliser un TimeSpan. Personnellement, je ne suis intéressé par aucune de ces solutions, car aucun type ne reflète vraiment le concept que vous essayez de représenter - je considère les types de date / heure dans .NET comme plutôt clairsemés, ce qui est l'une des raisons pour lesquelles j'ai commencé Noda Time . Dans Noda Time, vous pouvez utiliser leLocalTime type pour représenter une heure de la journée.

Une chose à considérer: l'heure n'est pas nécessairement la durée écoulée depuis minuit le même jour ...

(En outre, si vous souhaitez également représenter l' heure de fermeture d'une boutique, vous pouvez constater que vous souhaitez représenter 24h00, c'est-à-dire l'heure de fin de journée. La plupart des API de date / heure - y compris Noda Heure - ne permettez pas que cela soit représenté comme une valeur de l'heure.)

Jon Skeet
la source
5
"[L] 'heure du jour n'est pas nécessairement la durée écoulée depuis minuit le même jour ..." L'heure d'été est-elle la seule raison? Juste curieux de savoir pourquoi vous l'avez laissé indéfini.
jason
14
@Jason: L'heure d'été est la seule raison pour laquelle je peux penser spontanément - ignorer les secondes intercalaires comme non pertinentes pour la plupart des applications. Je l'ai surtout laissé de cette façon pour encourager les autres à réfléchir à la raison. Je pense que c'est une bonne chose pour les gens de réfléchir un peu plus aux dates / heures qu'ils ne le font actuellement :)
Jon Skeet
LocalTime est exactement ce dont j'ai besoin pour répondre à mes besoins.
sduplooy
1
@sduplooy: Envie de nous aider à le porter depuis Joda Time alors? :)
Jon Skeet
1
@Oakcool: Exactement comme je l'ai dit le 18 mai: Durationdans Noda Time, ou TimeSpandans la BCL. J'encapsulerais probablement la «place dans la vidéo + le commentaire» comme type, puis j'aurais un tableau de ce type.
Jon Skeet
164

Vous pouvez utiliser la durée

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

[Modifier]
Compte tenu des autres réponses et de la modification de la question, j'utiliserais toujours TimeSpan. Inutile de créer une nouvelle structure là où une structure existante du framework suffit.
Sur ces lignes, vous finiriez par dupliquer de nombreux types de données natifs.

John G
la source
19
Exactement. DateTime utilise TimeSpan à cet effet. Doc pour DateTime.TimeSpan, propriété: "Un TimeSpan qui représente la fraction de la journée qui s'est écoulée depuis minuit."
Marcel Jackwerth
4
TimeSpan indique un intervalle alors que le temps dont je parle n'est pas un intervalle, mais un point fixe unique sur une plage de dates.
sduplooy
3
Il peut être utilisé comme un point fixe, et comme vous l'avez spécifié dans la question, il est sans date. Après tout, vous décidez comment utiliser ces types de données à votre avantage.
John G
9
@John G: Bien qu'il puisse être utilisé pour représenter un point fixe, je suis d'accord avec l'OP - surcharger l'utilisation de TimeSpance type est quelque peu moche. C'est le meilleur qui soit disponible dans le cadre lui-même, mais ce n'est pas la même chose que de dire que c'est agréable.
Jon Skeet
5
À partir de .Net 3.5, MSDN documente que "La structure TimeSpan peut également être utilisée pour représenter l'heure de la journée, mais uniquement si l'heure n'est pas liée à une date particulière.". En d'autres termes, c'est exactement la solution à la question proposée.
Pharap
34

Si ce vide Datevous dérange vraiment, vous pouvez également créer une Timestructure plus simple :

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

Ou, pourquoi vous déranger: si vous n'avez pas besoin de faire de calcul avec ces informations, enregistrez-les simplement sous String.

Rubens Farias
la source
2
Hmmm ... peut-être ... mais pourquoi réinventer la roue? Si le langage a déjà une classe / structure (ce que font C # et VB.NET), alors allez-y. Mais je comprends où vous essayez d'aller avec votre réponse.
Kris Krause
1
En quoi cette structure serait-elle différente de TimeSpan, cela ne ferait que la dupliquer d'une certaine manière.
John G
4
Vous voter en raison de l'existence de TimeSpan, qui gère déjà cela, et d'une manière bien meilleure.
Noon Silk
1
@silky, j'ai écrit ceci après avoir lu la première réponse; OP a déclaré à la question qu'il ne voulait pas utiliser TimeSpan; Personnellement, je choisirais d'utiliser un DateTime ordinaire
Rubens Farias
18
+1 C'est mieux qu'un TimeSpan car il a moins de possibilités d'interprétation erronée ... un TimeSpan est vraiment destiné à être utilisé comme un intervalle (voir MSDN) donc une propriété comme Days n'a aucune signification lorsque TimeSpan est utilisé comme Time
Zaid Masud
20

Je dis utiliser un DateTime. Si vous n'avez pas besoin de la partie date, ignorez-la. Si vous avez besoin d'afficher uniquement l'heure à l'utilisateur, affichez-la comme suit:

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

Il semble que tout le travail supplémentaire nécessaire pour créer une nouvelle classe ou même utiliser un TimeSpan ne soit pas nécessaire.

bugfixr
la source
Comment afficheriez-vous les secondes et les millisecondes dans cette méthode?
Mona Jalal
5
@MonaJalal Millisecondes: DateTime.Now.ToString("hh:mm:ss.fff");Microsecondes: DateTime.Now.ToString("hh:mm:ss.ffffff");Nanosecondes (si DateTime a même autant de résolution): DateTime.Now.ToString("hh:mm:ss.fffffffff");Selon MSDN
Pharap
2
Ainsi, les 5 à 10 minutes nécessaires pour implémenter un type approprié vous semblent plus de travail que d'avoir à considérer dans l'ensemble de la base de code, pour tout développement futur, qu'une propriété DateTime peut contenir uniquement une heure et doit être formatée comme ça dans ces scénarios, et la partie date pourrait devoir être ignorée? Amusez-vous à déboguer les occurrences où vous trouverez "0001-01-01 10:00" dans votre base de données, dans les communications externes, etc ....
MarioDS
10

Je pense que la classe de Rubens est une bonne idée, alors j'ai pensé à faire un échantillon immuable de sa classe Time avec une validation de 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);
    }
}
Chibueze Opata
la source
La validation que vous avez ajoutée est extrêmement importante. Le principal inconvénient de la classe TimeSpan dans la modélisation d'une heure est que l'heure peut dépasser 24 heures.
shelbypereira
Pourquoi les heures, minutes et secondes utilisent-elles int et non uint? S'il n'y a aucune raison, je pense qu'ils peuvent directement utiliser uint et cela évite le casting dans le constructeur.
shelbypereira
6

En plus de 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
la source
Vos méthodes d'ajout pour les minutes et les secondes sont erronées car elles ne tiennent pas compte des valeurs supérieures à 59.
Chibueze Opata
@Chibueze Opate: vous avez tout à fait raison. C'était juste rapide et sale. Je devrais mettre un peu plus de travail dans ce code. Je le mettrai à jour plus tard ... Merci pour votre conseil!
Jules
5

Voici une classe TimeOfDay complète.

C'est exagéré pour les cas simples, mais si vous avez besoin de fonctionnalités plus avancées comme je l'ai fait, cela peut vous aider.

Il peut gérer les cas d'angle, certains calculs de base, les comparaisons, l'interaction avec DateTime, l'analyse, etc.

Vous trouverez ci-dessous le code source de la classe TimeOfDay. Vous pouvez voir des exemples d'utilisation et en savoir plus ici :

Cette classe utilise DateTime pour la plupart de ses calculs et comparaisons internes afin que nous puissions tirer parti de toutes les connaissances déjà intégrées dans 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
la source
2

Si vous ne souhaitez pas utiliser un DateTime ou TimeSpan, et souhaitez simplement stocker l'heure, vous pouvez simplement stocker les secondes depuis minuit dans un Int32, ou (si vous ne voulez même pas des secondes) les minutes depuis minuit. s'inscrirait dans un Int16. Il serait trivial d'écrire les quelques méthodes nécessaires pour accéder aux heures, minutes et secondes à partir d'une telle valeur.

La seule raison pour laquelle je peux penser à éviter DateTime / TimeSpan serait si la taille de la structure est critique.

(Bien sûr, si vous utilisez un schéma simple comme celui ci-dessus enveloppé dans une classe, il serait également trivial de remplacer le stockage par un TimeSpan à l'avenir si vous réalisez soudainement que cela vous donnerait un avantage)

Jason Williams
la source