Làm thế nào để tôi đại diện cho một giá trị thời gian chỉ trong .NET?


238

Có cách nào người ta có thể biểu thị giá trị thời gian chỉ trong .NET mà không có ngày không? Ví dụ, cho biết thời gian mở cửa hàng?

TimeSpanchỉ ra một phạm vi, trong khi tôi chỉ muốn lưu trữ một giá trị thời gian. Sử dụng DateTimeđể chỉ ra điều này sẽ dẫn đến cái mới DateTime(1,1,1,8,30,0)không thực sự mong muốn.

Câu trả lời:


143

Như những người khác đã nói, bạn có thể sử dụng a DateTimevà bỏ qua ngày, hoặc sử dụng a TimeSpan. Cá nhân tôi không quan tâm đến một trong hai giải pháp này, vì cả hai loại này đều không phản ánh đúng khái niệm mà bạn đang cố gắng thể hiện - Tôi coi các loại ngày / giờ trong .NET là một trong những lý do khiến tôi bắt đầu Giờ Noda . Trong Noda Time, bạn có thể sử dụng LocalTimeloại để thể hiện thời gian trong ngày.

Một điều cần xem xét: thời gian trong ngày không nhất thiết là khoảng thời gian kể từ nửa đêm cùng ngày ...

(Ngoài ra, nếu bạn cũng muốn đại diện cho thời gian đóng cửa của cửa hàng, bạn có thể thấy rằng bạn muốn đại diện cho 24:00, tức là thời gian vào cuối ngày. Hầu hết các API ngày / giờ - bao gồm Noda Thời gian - không cho phép điều đó được biểu thị dưới dạng giá trị thời gian trong ngày.)


5
"[T] anh ấy thời gian trong ngày không nhất thiết là thời gian kể từ nửa đêm cùng ngày ..." Có phải thời gian tiết kiệm ánh sáng ban ngày là lý do duy nhất? Chỉ tò mò tại sao bạn lại để nó vô thời hạn.
jason

14
@Jason: Tiết kiệm ánh sáng ban ngày là lý do duy nhất tôi có thể nghĩ đến bằng tay - bỏ qua những giây nhuận không liên quan đến hầu hết các ứng dụng. Tôi chủ yếu để nó theo cách đó để khuyến khích người khác nghĩ tại sao điều đó có thể. Tôi cho rằng mọi người nên suy nghĩ sâu sắc hơn về ngày / lần so với hiện tại :)
Jon Skeet

LocalTime chính xác là những gì tôi cần để hỗ trợ yêu cầu của tôi.
sduplooy

1
@sduplooy: Fancy giúp chúng tôi chuyển nó từ Joda Time rồi chứ? :)
Jon Skeet

1
@Oakcool: Chính xác như tôi đã nói vào ngày 18 tháng 5: Durationtrong Noda Time, hoặc TimeSpantrong BCL. Tôi có thể gói gọn "địa điểm trong video + nhận xét" dưới dạng một loại và sau đó có một mảng loại đó.
Jon Skeet

164

Bạn có thể sử dụng thời gian

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

[Chỉnh sửa]
Xem xét các câu trả lời khác và chỉnh sửa cho câu hỏi, tôi vẫn sẽ sử dụng TimeSpan. Không có điểm nào trong việc tạo ra một cấu trúc mới trong đó một cấu trúc hiện có từ khung là đủ.
Trên các dòng này, bạn sẽ sao chép nhiều loại dữ liệu gốc.


19
Chính xác. DateTime sử dụng TimeSpan cho chính xác mục đích đó. Tài liệu cho tài sản DateTime.TimeSpan: "Một TimeSpan đại diện cho phần nhỏ của ngày đã trôi qua kể từ nửa đêm."
Marcel Jackwerth

4
TimeSpan chỉ ra một khoảng trong khi thời gian tôi đang nói không phải là một khoảng, mà là một điểm cố định duy nhất trong một phạm vi ngày.
sduplooy

3
Nó có thể được sử dụng như một điểm cố định, và như bạn đã chỉ định trong câu hỏi, nó không có ngày. Sau khi tất cả, bạn quyết định làm thế nào để sử dụng các kiểu dữ liệu này cho benifit của bạn.
John G

9
@ John G: Mặc dù nó có thể được sử dụng để thể hiện một điểm cố định, tôi đồng ý với OP - quá tải việc sử dụng TimeSpannhư thế này là hơi xấu. Đó là thứ tốt nhất có sẵn trong chính khuôn khổ, nhưng nó không giống như nói nó dễ chịu.
Jon Skeet

5
Kể từ .Net 3.5, tài liệu MSDN "Cấu trúc TimeSpan cũng có thể được sử dụng để biểu thị thời gian trong ngày, nhưng chỉ khi thời gian không liên quan đến một ngày cụ thể." Nói cách khác, đây chính xác là giải pháp cho câu hỏi được đề xuất.
Pharap

34

Nếu khoảng trống đó Datethực sự làm phiền bạn, bạn cũng có thể tạo một Timecấu trúc đơn giản hơn :

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

Hoặc, tại sao phải bận tâm: nếu bạn không cần thực hiện bất kỳ tính toán nào với thông tin đó, chỉ cần lưu trữ dưới dạng String.


2
Hmmm ... có thể ... nhưng tại sao lại phát minh lại bánh xe? Nếu ngôn ngữ đã có một lớp / cấu trúc (mà C # và VB.NET làm), thì hãy đi với nó. Nhưng tôi hiểu bạn đang cố gắng đi đâu với câu trả lời của bạn.
Kris Krause

1
Cấu trúc này sẽ khác với TimeSpan như thế nào, điều này sẽ nhân đôi nó theo một cách.
John G

4
Đánh giá thấp bạn do sự tồn tại của TimeSpan, đã xử lý việc này, và theo cách tốt hơn đáng kể.
Trưa Silk

1
@silky, tôi đã viết bài này sau khi đọc câu trả lời đầu tiên; OP nói về câu hỏi mà anh ấy không muốn sử dụng TimeSpan; Cá nhân tôi sẽ chọn sử dụng DateTime đơn giản
Rubens Farias

18
1 Đây là tốt hơn so với một TimeSpan bởi vì nó có khả năng ít hiểu sai ... một TimeSpan là thực sự có nghĩa là để được sử dụng như một khoảng thời gian (xem MSDN) do đó, một tài sản như ngày không có ý nghĩa khi TimeSpan được sử dụng như một Time
Zaid Masud

20

Tôi nói sử dụng DateTime. Nếu bạn không cần phần ngày, chỉ cần bỏ qua nó. Nếu bạn chỉ cần hiển thị thời gian cho người dùng, hãy xuất nó được định dạng cho người dùng như thế này:

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

Có vẻ như tất cả các công việc bổ sung để tạo một lớp mới hoặc thậm chí sử dụng TimeSpan là không cần thiết.


Làm thế nào bạn sẽ hiển thị giây và mili-giây trong phương pháp này?
Mona Jalal

5
@MonaJalal mili giây: DateTime.Now.ToString("hh:mm:ss.fff");micro: DateTime.Now.ToString("hh:mm:ss.ffffff");nano giây (nếu DateTime thậm chí còn có độ phân giải mà nhiều): DateTime.Now.ToString("hh:mm:ss.fffffffff");Theo MSDN
Pharap

2
Vì vậy, mất từ ​​5 đến 10 phút để triển khai một loại thích hợp cho việc này có vẻ hiệu quả với bạn hơn là phải xem xét trong toàn bộ cơ sở mã, cho bất kỳ sự phát triển nào trong tương lai, rằng một thuộc tính DateTime chỉ có thể chứa thời gian và phải được định dạng như thế trong các kịch bản đó, và phần ngày có thể cần phải bỏ qua? Vui vẻ gỡ lỗi các lần xuất hiện mà bạn sẽ tìm thấy "0001-01-01 10:00" trong cơ sở dữ liệu của mình, trong các giao tiếp bên ngoài, v.v ....
MarioDS

10

Tôi nghĩ rằng lớp của Rubens là một ý tưởng tốt nên đã nghĩ sẽ tạo ra một mẫu bất biến của lớp Thời gian của mình với xác nhận cơ bản.

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

Xác nhận bạn đã thêm là vô cùng quan trọng. Hạn chế chính của lớp TimeSpan trong mô hình hóa thời gian là thời gian trong ngày có thể hơn 24 giờ.
Shelbypereira 23/07/19

Tại sao Giờ, phút và giây sử dụng int và không uint? Nếu không có lý do gì tôi nghĩ rằng họ có thể trực tiếp sử dụng uint và điều này sẽ tránh việc đúc trong hàm tạo.
Shelbypereira 23/07/19

6

Ngoài 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);
    }
}

Các phương thức thêm của bạn trong vài phút và giây là sai vì chúng không chiếm các giá trị trên 59.
Chibueze Opata

@Chibueze Opate: bạn hoàn toàn đúng. Điều này thật nhanh chóng và bẩn thỉu. Tôi nên đặt thêm một số công việc trong mã này. Tôi sẽ cập nhật nó sau ... Cảm ơn gợi ý của bạn!
Jules

5

Đây là một lớp TimeOfDay đầy đủ tính năng.

Điều này là quá mức cần thiết cho các trường hợp đơn giản, nhưng nếu bạn cần chức năng nâng cao hơn như tôi đã làm, điều này có thể giúp ích.

Nó có thể xử lý các trường hợp góc, một số phép toán cơ bản, so sánh, tương tác với DateTime, phân tích cú pháp, v.v.

Dưới đây là mã nguồn cho lớp TimeOfDay. Bạn có thể xem các ví dụ sử dụng và tìm hiểu thêm ở đây :

Lớp này sử dụng DateTime cho hầu hết các tính toán và so sánh bên trong của nó để chúng ta có thể tận dụng tất cả các kiến ​​thức đã được nhúng trong 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

Nếu bạn không muốn sử dụng DateTime hoặc TimeSpan và chỉ muốn lưu trữ thời gian trong ngày, bạn chỉ có thể lưu trữ giây kể từ nửa đêm trong Int32 hoặc (nếu bạn thậm chí không muốn vài giây) vài phút kể từ nửa đêm sẽ phù hợp với một Int16. Sẽ là tầm thường khi viết một vài phương thức cần thiết để truy cập Giờ, Phút và Thứ hai từ một giá trị như vậy.

Lý do duy nhất tôi có thể nghĩ ra để tránh DateTime / TimeSpan là nếu kích thước của cấu trúc là quan trọng.

(Tất nhiên, nếu bạn sử dụng một sơ đồ đơn giản như được bao bọc trong một lớp, thì việc thay thế bộ lưu trữ bằng TimeSpan trong tương lai sẽ rất đơn giản nếu bạn bất ngờ nhận ra rằng sẽ mang lại lợi thế cho bạn)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.