Como represento um valor somente de hora no .NET?

238

Existe uma maneira de representar um valor somente de hora no .NET sem a data? Por exemplo, indicando o horário de abertura de uma loja?

TimeSpanindica um intervalo, enquanto eu só quero armazenar um valor de tempo. Usar DateTimepara indicar isso resultaria em novo, o DateTime(1,1,1,8,30,0)que não é realmente desejável.

sduplooy
fonte

Respostas:

143

Como já foi dito, você pode usar DateTimeae ignorar a data ou a TimeSpan. Pessoalmente, não estou interessado em nenhuma dessas soluções, pois nenhum dos dois reflete realmente o conceito que você está tentando representar - considero os tipos de data / hora no .NET um pouco do lado escasso, e esse é um dos motivos pelos quais comecei. Hora de Noda . No Noda Time, você pode usar o LocalTimetipo para representar uma hora do dia.

Uma coisa a considerar: a hora do dia não é necessariamente a duração desde a meia-noite do mesmo dia ...

(Como outro aparte, se você também deseja representar o horário de fechamento de uma loja, pode achar que deseja representar 24:00, ou seja, o horário no final do dia. A maioria das APIs de data / hora - incluindo Noda Hora - não permita que isso seja representado como um valor da hora do dia.)

Jon Skeet
fonte
5
"[A] hora do dia não é necessariamente o período desde a meia-noite do mesmo dia ..." O horário de verão é a única razão? Apenas curioso por que você deixou indefinido.
jason
14
@ Jason: O horário de verão é a única razão pela qual posso pensar de antemão - ignorar os segundos bissextos como irrelevantes para a maioria dos aplicativos. Na maioria das vezes, deixei assim para incentivar os outros a pensar por que isso poderia ser. Eu acho que é uma coisa boa para as pessoas a pensar um pouco mais profundamente sobre datas / horários do que fazem atualmente :)
Jon Skeet
LocalTime é exatamente o que eu preciso para suportar meus requisitos.
precisa saber é o seguinte
1
@sduplooy: Gosta de nos ajudar a transportá-lo do Joda Time, então? :)
Jon Skeet
1
@Oakcool: Exatamente como eu disse em 18 de maio: Durationno Noda Time, ou TimeSpanno BCL. Eu provavelmente encapsularia o "lugar no vídeo + comentário" como um tipo e, em seguida, teria uma matriz desse tipo.
Jon Skeet
164

Você pode usar o período

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

[Editar]
Considerando as outras respostas e a edição da pergunta, eu ainda usaria o TimeSpan. Não faz sentido criar uma nova estrutura em que uma estrutura existente seja suficiente.
Nessas linhas, você acabaria duplicando muitos tipos de dados nativos.

John G
fonte
19
Exatamente. DateTime usa TimeSpan exatamente para esse fim. Doc para a propriedade DateTime.TimeSpan: "Um TimeSpan que representa a fração do dia decorrido desde a meia-noite."
Marcel Jackwerth
4
TimeSpan indica um intervalo, enquanto o tempo que estou falando não é um intervalo, mas um único ponto fixo em um intervalo de datas.
precisa saber é o seguinte
3
Pode ser usado como um ponto fixo no poço e, como você especificou na pergunta, é sem data. Afinal, você decide como usar esses tipos de dados para o seu benefício.
John L
9
@ John G: Embora possa ser usado para representar um ponto fixo, concordo com o OP - sobrecarregar o uso desse TimeSpantipo é um pouco feio. É o melhor que está disponível no próprio framework, mas não é o mesmo que dizer que é agradável.
precisa
5
A partir do .Net 3.5, o MSDN documenta que "A estrutura TimeSpan também pode ser usada para representar a hora do dia, mas somente se a hora não estiver relacionada a uma data específica". Em outras palavras, essa é exatamente a solução para a questão proposta.
Pharap
34

Se esse vazio Daterealmente o incomoda, você também pode criar uma Timeestrutura mais simples :

// 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 por que se preocupar: se você não precisar fazer nenhum cálculo com essas informações, armazene-as como String.

Rubens Farias
fonte
2
Hmmm ... talvez ... mas por que reinventar a roda? Se o idioma já tiver uma classe / estrutura (que C # e VB.NET possuem), siga-o. Mas eu entendo para onde você está tentando ir com sua resposta.
Kris Krause
1
Como essa estrutura seria diferente do TimeSpan, isso seria apenas uma duplicação.
John L
4
Voto você devido à existência de TimeSpan, que já lida com isso, e de uma maneira significativamente melhor.
Noon Silk
1
@ sky, eu escrevi isso depois de ler a primeira resposta; OP disse em questão que não queria usar o TimeSpan; Eu, pessoalmente, optaria por usar um simples DateTime
Rubens Farias
18
+1 Isso é melhor que um TimeSpan porque tem menos possibilidades de erros de interpretação ... um TimeSpan é realmente destinado a ser usado como um intervalo (consulte o MSDN), portanto, uma propriedade como Days não tem significado quando TimeSpan é usado como Time
Zaid Masud
20

Eu digo usar um DateTime. Se você não precisar da parte da data, ignore-a. Se você precisar exibir apenas a hora para o usuário, faça a saída formatada para o usuário desta forma:

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

Parece que todo o trabalho extra de criar uma nova classe ou até mesmo usar um TimeSpan é desnecessário.

bugfixr
fonte
Como você mostraria segundos e mili-segundos neste método?
Mona Jalal
5
@MonaJalal milissegundos: DateTime.Now.ToString("hh:mm:ss.fff");microssegundos: DateTime.Now.ToString("hh:mm:ss.ffffff");nanossegundos (se DateTime tem mesmo que muito resolução): DateTime.Now.ToString("hh:mm:ss.fffffffff");Como por MSDN
Pharap
2
Portanto, os 5 a 10 minutos necessários para implementar um tipo adequado para isso parecem mais trabalhosos do que considerar em toda a base de código, para qualquer desenvolvimento futuro, que uma propriedade DateTime possa conter apenas um tempo e precise ser formatada assim nesses cenários, e a parte da data pode precisar ser ignorada? Divirta-se depurando as ocorrências onde você vai encontrar "0001-01-01 10:00" em seu banco de dados, em comunicações externas, etc ....
MarioDS
10

Eu acho que a aula de Rubens é uma boa idéia, então pensei em fazer uma amostra imutável da aula Time com validação 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
fonte
A validação que você adicionou é extremamente importante. A principal desvantagem da classe TimeSpan na modelagem de uma hora é que a hora do dia pode ser superior a 24 horas.
Shelbypereira 23/07/19
Por que as horas, minutos e segundos estão usando int e não uint? Se não houver razão, acho que eles podem usar diretamente o uint e isso evita a conversão no construtor.
Shelbypereira 23/07/19
6

Além do 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
fonte
Seus métodos add para minutos e segundos estão errados, pois não conta para valores acima de 59.
Chibueze Opata
@Chibueze Opate: você está completamente certo. Isso foi rápido e sujo. Eu deveria colocar mais trabalho nesse código. Vou atualizá-lo mais tarde ... Obrigado pela sua dica!
Jul4
5

Aqui está uma classe TimeOfDay com todos os recursos.

Isso é um exagero para casos simples, mas se você precisar de funcionalidades mais avançadas como eu, isso pode ajudar.

Ele pode lidar com os casos de canto, matemática básica, comparações, interação com DateTime, análise, etc.

Abaixo está o código fonte da classe TimeOfDay. Você pode ver exemplos de uso e saber mais aqui :

Essa classe usa o DateTime para a maioria de seus cálculos e comparações internas, para que possamos aproveitar todo o conhecimento já incorporado no 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
fonte
2

Se você não quiser usar um DateTime ou TimeSpan, e apenas desejar armazenar a hora do dia, poderá armazenar os segundos desde a meia-noite em um Int32 ou (se você não quiser segundos) os minutos desde a meia-noite caberia em um Int16. Seria trivial escrever os poucos métodos necessários para acessar a Hora, Minuto e Segundo a partir desse valor.

A única razão pela qual posso pensar em evitar DateTime / TimeSpan seria se o tamanho da estrutura fosse crítico.

(Obviamente, se você usar um esquema simples como o descrito acima em uma classe, também seria trivial substituir o armazenamento por um TimeSpan no futuro se você perceber de repente que isso lhe daria uma vantagem)

Jason Williams
fonte