Як я можу представити значення. Time only у .NET?


238

Чи є спосіб представити значення часу лише у .NET без дати? Наприклад, із зазначенням часу відкриття магазину?

TimeSpanвказує на діапазон, тоді як я хочу зберігати лише значення часу. Використання DateTimeдля позначення цього призведе до нового, DateTime(1,1,1,8,30,0)що не дуже бажано.

Відповіді:


144

Як уже говорили інші, ви можете використовувати DateTimeі ігнорувати дату або використовувати TimeSpan. Особисто я не захоплююсь жодним із цих рішень, так як жоден тип насправді не відображає концепцію, яку ви намагаєтесь представляти - я вважаю типи дати / часу в .NET як дещо на рідкій стороні, що є однією з причин, що я почав Час ноди . У Noda Time ви можете використовуватиLocalTime тип для відображення часу доби.

Варто враховувати одне: час доби необов’язково - це тривалість часу з півночі того ж дня ...

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


5
"[T] час доби необов'язково - це тривалість часу з півночі в той же день ..." Чи є літній час єдиною причиною? Просто цікаво, чому ви залишили це невизначено.
Язон

14
@Jason: Перехід на літній час - це єдина причина, з якої я можу подумати без проблем - ігнорування високосних секунд як нерелевантних для більшості програм. Я здебільшого залишав це таким чином, щоб спонукати інших думати, чому це може бути. Я вважаю, що людям добре замислитися над датами / часом, ніж зараз:
Йон Скіт

LocalTime - саме те, що мені потрібно для підтримки моєї вимоги.
sduplooy

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 саме для цієї мети. Документ для властивості DateTime.TimeSpan: "Час часу, що представляє частку дня, що минув з півночі."
Марсель Джекверт

4
Проміжок часу вказує інтервал, тоді як час, про який я говорю, - це не інтервал, а єдина фіксована точка протягом діапазону дат.
sduplooy

3
Він може використовуватися як фіксований пункт на свердловині, і як ви вказали у запитанні, це не має дати. Зрештою, ви вирішите, як використовувати ці типи даних для вашої користі.
Іоанн G

9
@John G: Хоча це може бути використано для представлення фіксованої точки, я згоден з ОП - перевантаження використання TimeSpanподібного виглядає дещо негарно. Це найкраще, що є в самих рамках, але це не те саме, що говорити, що це приємно.
Джон Скіт

5
Станом на .Net 3.5, MSDN документує, що "Структуру TimeSpan також можна використовувати для відображення часу доби, але лише якщо час не пов'язаний з конкретною датою." Іншими словами, це саме рішення запропонованого питання.
Фарап

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, це лише певним чином її дублює.
Іоанн G

4
Відмовлятися від вас через існування TimeSpan, яке вже справляється з цим, і значно кращим чином.
Полудень Шовк

1
@silky, я написав це, прочитавши першу відповідь; ОП сказав на запитання, що не хоче використовувати TimeSpan; Я особисто вирішив би використати звичайний DateTime
Рубенс Фаріас

18
+1 Це краще, ніж TimeSpan, оскільки він має менше можливостей неправильного тлумачення ... TimeSpan дійсно призначений для використання як інтервал (див. MSDN), тому властивість типу Days не має значення, коли TimeSpan використовується як Time
Zaid Масуд

20

Я кажу, використовуйте DateTime. Якщо вам не потрібна частина дати, просто проігноруйте її. Якщо вам потрібно відображати користувачеві лише час, виведіть його в такому форматі користувачеві:

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

Здається, що вся зайва робота щодо створення нового класу або навіть використання TimeSpan непотрібна.


Як би ви показали секунди та мілі-секунди у цьому методі?
Mona Jalal

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

Я думаю, що клас Рубенса - це гарна ідея, тому я подумав зробити незмінний зразок свого часового класу з базовою валідацією.

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 годин.
shelbypereira

Чому Години, хвилини та секунди використовують 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 у майбутньому, якщо ви раптом зрозумієте, що це дасть вам перевагу)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.