Как мне представить значение только времени в .NET?

238

Есть ли способ представить значение времени в .NET без даты? Например, с указанием времени открытия магазина?

TimeSpanуказывает диапазон, тогда как я хочу сохранить только значение времени. Использование DateTimeдля указания этого приведет к новому, DateTime(1,1,1,8,30,0)что не очень желательно.

sduplooy
источник

Ответы:

144

Как уже говорили другие, вы можете использовать DateTimeи игнорировать дату или использовать TimeSpan. Лично я не заинтересован ни в одном из этих решений, так как ни один из типов действительно не отражает концепцию, которую вы пытаетесь представить - я считаю, что типы даты / времени в .NET несколько отстают, что является одной из причин, по которой я начал Время Нода . В Noda Time вы можете использовать LocalTimeтип для представления времени суток.

Нужно учитывать одну вещь: время суток не обязательно является продолжительностью времени с полуночи в тот же день ...

(Кроме того, если вы также хотите представить время закрытия магазина, вы можете обнаружить, что хотите представлять 24:00, то есть время в конце дня. Большинство API даты / времени - включая Noda Время - не допускайте, чтобы это было представлено как значение времени суток.)

Джон Скит
источник
5
«[T] время суток не обязательно является продолжительностью времени с полуночи в один и тот же день ...» Является ли летнее время единственной причиной? Просто любопытно, почему вы оставили это неопределенным.
Джейсон
14
@Jason: летнее время - единственная причина, которую я могу придумать, не обращая внимания - игнорирование високосных секунд не имеет отношения к большинству приложений. В основном я оставил это так, чтобы побудить других задуматься, почему это так. Я считаю, что для людей хорошо подумать о датах и ​​времени более глубоко, чем сейчас :)
Джон Скит
LocalTime - это именно то, что мне нужно для поддержки моего требования.
сдуплой
1
@sduplooy: Не хочешь помочь нам перенести его из Joda Time? :)
Джон Скит
1
@ Oakcool: Точно так же, как я сказал 18 мая: Durationв Noda Time или TimeSpanв BCL. Я бы, вероятно, инкапсулировал «место в видео + комментарий» как тип, а затем имел бы массив этого типа.
Джон Скит
164

Вы можете использовать временной интервал

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

[Править]
Учитывая другие ответы и редактирование вопроса, я бы все равно использовал TimeSpan. Нет смысла создавать новую структуру, в которой достаточно существующей структуры.
В этих строках вы в конечном итоге продублировали бы много собственных типов данных.

Джон Г
источник
19
Именно. DateTime использует TimeSpan именно для этой цели. Свойство Doc для DateTime.TimeSpan: «TimeSpan, представляющий часть дня, прошедшего с полуночи».
Марсель Джекверт
4
TimeSpan указывает интервал, тогда как время, о котором я говорю, - это не интервал, а одна фиксированная точка в диапазоне дат.
сдуплой
3
Он может использоваться как фиксированная точка на скважине, и, как вы указали в вопросе, он без даты. В конце концов, вы решаете, как использовать эти типы данных для вашей выгоды.
Джон Г
9
@Джон Дж .: Хотя это может использоваться для представления фиксированной точки, я согласен с ФП - перегрузка использования TimeSpanподобного рода несколько уродлива. Это лучшее, что доступно в самой структуре, но это не то же самое, что сказать, что это приятно.
Джон Скит
5
Начиная с .Net 3.5, MSDN документирует, что «структура TimeSpan также может использоваться для представления времени суток, но только в том случае, если время не связано с конкретной датой». Другими словами, это как раз и есть решение предложенного вопроса.
Pharap
34

Если эта пустота Dateдействительно вас беспокоит, вы также можете создать более простую Timeструктуру:

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

Или зачем беспокоиться: если вам не нужно делать какие-либо вычисления с этой информацией, просто сохраните ее как String.

Рубенс Фариас
источник
2
Хм ... может быть ... но зачем изобретать велосипед? Если у языка уже есть класс / структура (что делают в C # и VB.NET), то используйте его. Но я понимаю, куда вы пытаетесь пойти со своим ответом.
Крис Краузе
1
Чем бы эта структура отличалась от TimeSpan, это просто дублировало бы ее.
Джон Г
4
Отказаться от вас благодаря существованию TimeSpan, которое уже справляется с этим, и значительно лучше.
Шелковый полдень
1
@silky, я написал это после прочтения первого ответа; ОП ответил на вопрос, что не хочет использовать TimeSpan; Я лично предпочел бы использовать простой DateTime
Rubens Farias
18
+1 Это лучше, чем TimeSpan, потому что у него меньше возможностей неверного истолкования ... TimeSpan действительно предназначен для использования в качестве интервала (см. MSDN), поэтому свойство типа Days не имеет смысла, когда TimeSpan используется в качестве Time
Zaid Масуд
20

Я говорю использовать DateTime. Если вам не нужна часть даты, просто игнорируйте ее. Если вам нужно отобразить только время для пользователя, выведите его в формате для пользователя следующим образом:

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

Кажется, что вся дополнительная работа по созданию нового класса или даже по использованию TimeSpan не нужна.

bugfixr
источник
Как бы вы показали секунды и милисекунды в этом методе?
Мона Джалал
5
@MonaJalal Миллисекунды: DateTime.Now.ToString("hh:mm:ss.fff");микросекунды: DateTime.Now.ToString("hh:mm:ss.ffffff");наносекунды (если даже у DateTime такое большое разрешение): DateTime.Now.ToString("hh:mm:ss.fffffffff");согласно MSDN
Pharap
2
Таким образом, от 5 до 10 минут, необходимых для реализации правильного типа, для вас кажется более трудоемким, чем необходимость во всей будущей разработке учитывать во всей кодовой базе, что свойство DateTime может содержать только время и должно быть отформатировано. как в тех сценариях, и часть даты может быть проигнорирована? Получайте удовольствие отлаживая события, где вы найдете «0001-01-01 10:00» в своей базе данных, во внешних коммуникациях и т. Д.
MarioDS
10

Я думаю, что урок Рубенса - хорошая идея, поэтому я подумал сделать неизменный образец своего класса Time с базовой проверкой.

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);
    }
}
Чибуэз Опата
источник
Проверка, которую вы добавили, чрезвычайно важна. Основным недостатком класса TimeSpan при моделировании времени является то, что время суток может быть больше 24 часов.
Шелбиперейра
Почему часы, минуты и секунды используют int, а не uint? Если нет причин, я думаю, что они могут напрямую использовать uint, и это позволяет избежать приведения в конструкторе.
Shelbypereira
6

В дополнение к 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);
    }
}
Жюль
источник
Ваши методы добавления для минут и секунд неверны, поскольку они не учитывают значения выше 59.
Chibueze Opata
@Chibueze Opate: вы совершенно правы. Это было просто быстро и грязно. Я должен положить еще немного работы в этом коде. Я обновлю это позже ... Спасибо за Вашу подсказку!
Жюль
5

Вот полнофункциональный класс TimeOfDay.

Для простых случаев это излишне, но если вам нужна более продвинутая функциональность, как я, это может помочь.

Он может обрабатывать угловые случаи, некоторую базовую математику, сравнения, взаимодействие с DateTime, анализ и т. Д.

Ниже приведен исходный код класса TimeOfDay. Вы можете увидеть примеры использования и узнать больше здесь :

Этот класс использует DateTime для большинства своих внутренних вычислений и сравнений, чтобы мы могли использовать все знания, уже встроенные в 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

Если вы не хотите использовать DateTime или TimeSpan и просто хотите сохранить время суток, вы можете просто сохранить секунды с полуночи в Int32 или (если вы даже не хотите секунд) минуты с полуночи будет вписываться в Int16. Было бы тривиально написать несколько методов, необходимых для доступа к часам, минутам и секундам из такого значения.

Единственная причина, по которой я могу избежать DateTime / TimeSpan, заключается в том, что размер структуры имеет решающее значение.

(Конечно, если вы используете простую схему, подобную описанной выше, в классе, то в будущем также будет тривиально заменить хранилище на TimeSpan, если вы вдруг поймете, что это даст вам преимущество)

Джейсон Уильямс
источник