¿Cómo represento un valor de solo tiempo en .NET?

238

¿Hay alguna manera de representar un valor de tiempo solo en .NET sin la fecha? Por ejemplo, ¿indica el horario de apertura de una tienda?

TimeSpanindica un rango, mientras que solo quiero almacenar un valor de tiempo. Usar DateTimepara indicar esto daría como resultado algo nuevo DateTime(1,1,1,8,30,0)que no es realmente deseable.

sduplooy
fuente

Respuestas:

144

Como otros han dicho, puede usar DateTimeay ignorar la fecha, o usar a TimeSpan. Personalmente, no estoy interesado en ninguna de estas soluciones, ya que ninguno de los tipos realmente refleja el concepto que está tratando de representar: considero que los tipos de fecha / hora en .NET son algo dispersos, que es una de las razones por las que comencé Noda Time . En Noda Time, puede usar el LocalTimetipo para representar una hora del día.

Una cosa a tener en cuenta: la hora del día no es necesariamente la cantidad de tiempo transcurrida desde la medianoche del mismo día ...

(Como otro lado, si también desea representar la hora de cierre de una tienda, es posible que desee representar las 24:00, es decir, la hora al final del día. La mayoría de las API de fecha / hora, incluida Noda Hora: no permita que se represente como un valor de hora del día).

Jon Skeet
fuente
55
"[La] hora del día no es necesariamente el tiempo transcurrido desde la medianoche del mismo día ..." ¿Es el horario de verano la única razón? Es curioso por qué lo dejaste indefinido.
Jason
14
@Jason: El horario de verano es la única razón por la que puedo pensar de manera informal: ignorar los segundos bisiestos como irrelevantes para la mayoría de las aplicaciones. Principalmente lo dejé así para alentar a otros a pensar por qué podría ser eso. Creo que es bueno que la gente piense un poco más profundamente sobre las fechas / horas de lo que lo hacen actualmente :)
Jon Skeet
LocalTime es exactamente lo que necesito para cumplir con mis requisitos.
sduplooy
1
@dudulooy: ¿Te apetece ayudarnos a transportarlo desde Joda Time entonces? :)
Jon Skeet
1
@Oakcool: Exactamente como dije el 18 de mayo: Durationen Noda Time o TimeSpanen BCL. Probablemente encapsule el "lugar en video + comentario" como un tipo, y luego tenga una matriz de ese tipo.
Jon Skeet
164

Puedes usar el intervalo de tiempo

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

[Editar]
Teniendo en cuenta las otras respuestas y la edición de la pregunta, todavía usaría TimeSpan. No tiene sentido crear una nueva estructura donde sea suficiente una existente del marco.
En estas líneas, terminaría duplicando muchos tipos de datos nativos.

John G
fuente
19
Exactamente. DateTime usa TimeSpan para exactamente ese propósito. Doc. Para DateTime.TimeSpan Propiedad: "Un TimeSpan que representa la fracción del día que ha transcurrido desde la medianoche".
Marcel Jackwerth
44
TimeSpan indica un intervalo, mientras que el tiempo del que estoy hablando no es un intervalo, sino un único punto fijo en un rango de fechas.
sduplooy
3
Puede usarse como un punto fijo en el pozo y, como especificó en la pregunta, no tiene fecha. Después de todo, usted decide cómo usar estos tipos de datos para su beneficio.
John G
99
@John G: Si bien se puede usar para representar un punto fijo, estoy de acuerdo con el OP: sobrecargar el uso de TimeSpaneste modo es algo feo. Es lo mejor que está disponible dentro del propio marco, pero eso no es lo mismo que decir que es agradable.
Jon Skeet
55
A partir de .Net 3.5, MSDN documenta que "la estructura TimeSpan también se puede utilizar para representar la hora del día, pero solo si la hora no está relacionada con una fecha en particular". En otras palabras, esta es exactamente la solución a la pregunta propuesta.
Pharap
34

Si ese vacío Daterealmente te molesta, también puedes crear una Timeestructura más 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);
    }
}

O, por qué molestarse: si no necesita hacer ningún cálculo con esa información, simplemente guárdelo como String.

Rubens Farias
fuente
2
Hmmm ... tal vez ... pero ¿por qué reinventar la rueda? Si el lenguaje ya tiene una clase / estructura (que C # y VB.NET tienen), entonces vaya con él. Pero entiendo a dónde estás tratando de llegar con tu respuesta.
Kris Krause
1
¿Cómo sería esta estructura diferente de TimeSpan? Esto simplemente la duplicaría de alguna manera.
John G
44
Votarte por la existencia de TimeSpan, que ya maneja esto, y de una manera significativamente mejor.
Mediodía Seda
1
@ Silky, escribí esto después de leer la primera respuesta; OP dijo en una pregunta que no quería usar TimeSpan; Yo, personalmente, optaría por usar un simple DateTime
Rubens Farias
18
+1 Esto es mejor que un TimeSpan porque tiene menos posibilidades de malinterpretar ... un TimeSpan realmente está destinado a usarse como un intervalo (ver MSDN), por lo que una propiedad como Days no tiene significado cuando TimeSpan se usa como Time
Zaid Masud
20

Yo digo que use una fecha y hora. Si no necesita la parte de la fecha, simplemente ignórela. Si necesita mostrar solo el tiempo al usuario, envíelo con el formato siguiente:

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

Parece que todo el trabajo extra de hacer una nueva clase o incluso usar un TimeSpan es innecesario.

bugfixr
fuente
¿Cómo mostrarías segundos y milisegundos en este método?
Mona Jalal
55
@MonaJalal Milisegundos: DateTime.Now.ToString("hh:mm:ss.fff");Microsegundos: DateTime.Now.ToString("hh:mm:ss.ffffff");Nanosegundos (si DateTime incluso tiene tanta resolución): DateTime.Now.ToString("hh:mm:ss.fffffffff");Según MSDN
Pharap
2
Entonces, los 5 a 10 minutos que lleva implementar un tipo adecuado para esto le parecen más trabajo que tener que considerar en toda la base de código, para cualquier desarrollo futuro, que una propiedad DateTime puede contener solo una hora y debe formatearse así en esos escenarios, y la porción de la fecha podría ser ignorada? Divertirse depuración de los sucesos en los que se encuentra "0001-01-01 10:00" en su base de datos, en las comunicaciones externas, etc ....
MarioDS
10

Creo que la clase de Rubens es una buena idea, así que pensé hacer una muestra inmutable de su clase Time con validación básica.

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
fuente
La validación que ha agregado es extremadamente importante. El principal inconveniente de la clase TimeSpan en el modelado de una hora es que la hora del día puede ser más de 24 horas.
shelbypereira
¿Por qué las horas, minutos y segundos usan int y no uint? Si no hay ninguna razón, creo que pueden usar directamente uint y esto evita la conversión en el constructor.
shelbypereira
6

Además 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
fuente
Sus métodos de agregar minutos y segundos son incorrectos ya que no representan valores superiores a 59.
Chibueze Opata
@Chibueze Opate: tienes toda la razón. Esto fue rápido y sucio. Debería poner más trabajo en este código. Lo actualizaré más tarde ... ¡Gracias por su sugerencia!
Julio
5

Aquí hay una clase completa de TimeOfDay.

Esto es excesivo para casos simples, pero si necesita una funcionalidad más avanzada como lo hice, esto puede ayudar.

Puede manejar los casos de esquina, algunas matemáticas básicas, comparaciones, interacción con DateTime, análisis, etc.

A continuación se muestra el código fuente de la clase TimeOfDay. Puede ver ejemplos de uso y obtener más información aquí :

Esta clase usa DateTime para la mayoría de sus cálculos internos y comparaciones para que podamos aprovechar todo el conocimiento ya incorporado en 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
fuente
2

Si no desea utilizar un DateTime o TimeSpan, y solo desea almacenar la hora del día, puede almacenar los segundos desde la medianoche en un Int32, o (si ni siquiera quiere segundos) los minutos desde la medianoche encajaría en un Int16. Sería trivial escribir los pocos métodos necesarios para acceder a la Hora, Minuto y Segundo desde dicho valor.

La única razón por la que puedo pensar para evitar DateTime / TimeSpan sería si el tamaño de la estructura es crítico.

(Por supuesto, si utiliza un esquema simple como el anterior incluido en una clase, también sería trivial reemplazar el almacenamiento con un TimeSpan en el futuro si de repente se da cuenta de que eso le daría una ventaja)

Jason Williams
fuente