共用方式為


如何使用 DateOnly 和 TimeOnly 結構

DateOnlyTimeOnly 結構是使用 .NET 6 引進的,分別代表特定的日期或日期時間。 在 .NET 6 之前,且一律在 .NET Framework 中,開發人員會使用 DateTime 類型(或其他替代方式)來代表下列其中一項:

  • 整個日期和時間。
  • 日期,忽略時間。
  • 僅考慮時間,不計日期。

DateOnlyTimeOnly 是代表 DateTime 類型特定部分的類型。

重要

DateOnlyTimeOnly 類型不適用於 .NET Framework。

DateOnly 結構

DateOnly 結構代表一個不包括時間的特定日期。 因為它沒有時間元件,所以代表從一天開始到一天結束的日期。 此結構很適合用來儲存特定日期,例如出生日期、周年日期或商務相關日期。

雖然您可以在忽略時間元件時使用 DateTime,但使用 DateOnlyDateTime有幾個優點:

  • 如果 DateTime 結構被時區位移,則 DateTime 結構可能會變換到前一天或第二天。 DateOnly 無法由時區位移,而且一律代表所設定的日期。

  • 串行化 DateTime 結構包含時間元件,這可能會遮蔽數據的意圖。 此外,DateOnly 序列化較少的數據量。

  • 當程式代碼與 SQL Server 等資料庫互動時,整個日期通常會儲存為 date 數據類型,但不包含時間。 DateOnly 更好地符合資料庫類型。

DateOnly 的範圍從 0001-01-01 到 9999-12-31,就像 DateTime一樣。 您可以在 DateOnly 建構函式中指定特定的行事曆。 不過,DateOnly 物件一律代表前置格里高利曆中的日期,不論使用哪一個曆法來建構它。 例如,您可以從希伯來歷建置日期,但日期會轉換成公曆:

var hebrewCalendar = new System.Globalization.HebrewCalendar();
var theDate = new DateOnly(5776, 2, 8, hebrewCalendar); // 8 Cheshvan 5776

Console.WriteLine(theDate);

/* This example produces the following output:
 *
 * 10/21/2015
*/
Dim hebrewCalendar = New System.Globalization.HebrewCalendar()
Dim theDate = New DateOnly(5776, 2, 8, hebrewCalendar) ' 8 Cheshvan 5776

Console.WriteLine(theDate)

' This example produces the following output
'
' 10/21/2015

DateOnly 範例

使用下列範例來瞭解 DateOnly

將 DateTime 轉換為 DateOnly

使用 DateOnly.FromDateTime static 方法,從 DateOnly 類型建立 DateTime 類型,如下列程式代碼所示:

var today = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine($"Today is {today}");

/* This example produces output similar to the following:
 * 
 * Today is 12/28/2022
*/
Dim today = DateOnly.FromDateTime(DateTime.Now)
Console.WriteLine($"Today is {today}")

' This example produces output similar to the following
' 
' Today is 12/28/2022

新增或減去天、月、年

有三種方法可用來調整 DateOnly 結構:AddDaysAddMonthsAddYears。 每個方法都會採用整數參數,並依該度量增加日期。 如果提供負數,日期會按該數值向前調整。 方法會傳回 DateOnly的新實例,因為 結構是不可變的。

var theDate = new DateOnly(2015, 10, 21);

var nextDay = theDate.AddDays(1);
var previousDay = theDate.AddDays(-1);
var decadeLater = theDate.AddYears(10);
var lastMonth = theDate.AddMonths(-1);

Console.WriteLine($"Date: {theDate}");
Console.WriteLine($" Next day: {nextDay}");
Console.WriteLine($" Previous day: {previousDay}");
Console.WriteLine($" Decade later: {decadeLater}");
Console.WriteLine($" Last month: {lastMonth}");

/* This example produces the following output:
 * 
 * Date: 10/21/2015
 *  Next day: 10/22/2015
 *  Previous day: 10/20/2015
 *  Decade later: 10/21/2025
 *  Last month: 9/21/2015
*/
Dim theDate = New DateOnly(2015, 10, 21)

Dim nextDay = theDate.AddDays(1)
Dim previousDay = theDate.AddDays(-1)
Dim decadeLater = theDate.AddYears(10)
Dim lastMonth = theDate.AddMonths(-1)

Console.WriteLine($"Date: {theDate}")
Console.WriteLine($" Next day: {nextDay}")
Console.WriteLine($" Previous day: {previousDay}")
Console.WriteLine($" Decade later: {decadeLater}")
Console.WriteLine($" Last month: {lastMonth}")

' This example produces the following output
' 
' Date: 10/21/2015
'  Next day: 10/22/2015
'  Previous day: 10/20/2015
'  Decade later: 10/21/2025
'  Last month: 9/21/2015

剖析和格式化 DateOnly

DateOnly 可以從字串剖析,就像 DateTime 結構一樣。 所有標準 .NET 以日期為基礎的剖析符號都適用於 DateOnly。 將 DateOnly 類型轉換成字串時,您也可以使用標準 .NET 日期格式設定模式。 如需格式化字串的詳細資訊,請參閱 標準日期和時間格式字串

var theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture);  // Custom format
var theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture);

Console.WriteLine(theDate.ToString("m", CultureInfo.InvariantCulture));     // Month day pattern
Console.WriteLine(theDate2.ToString("o", CultureInfo.InvariantCulture));    // ISO 8601 format
Console.WriteLine(theDate2.ToLongDateString());

/* This example produces the following output:
 * 
 * October 21
 * 2015-10-21
 * Wednesday, October 21, 2015
*/
Dim theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture) ' Custom format
Dim theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture)

Console.WriteLine(theDate.ToString("m", CultureInfo.InvariantCulture))     ' Month day pattern
Console.WriteLine(theDate2.ToString("o", CultureInfo.InvariantCulture))    ' ISO 8601 format
Console.WriteLine(theDate2.ToLongDateString())

' This example produces the following output
' 
' October 21
' 2015-10-21
' Wednesday, October 21, 2015

比較 DateOnly

DateOnly 可以與其他實例進行比較。 例如,您可以檢查日期是否在另一個日期之前或之後,或今天日期是否符合特定日期。

var theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture);  // Custom format
var theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture);
var dateLater = theDate.AddMonths(6);
var dateBefore = theDate.AddDays(-10);

Console.WriteLine($"Consider {theDate}...");
Console.WriteLine($" Is '{nameof(theDate2)}' equal? {theDate == theDate2}");
Console.WriteLine($" Is {dateLater} after? {dateLater > theDate} ");
Console.WriteLine($" Is {dateLater} before? {dateLater < theDate} ");
Console.WriteLine($" Is {dateBefore} after? {dateBefore > theDate} ");
Console.WriteLine($" Is {dateBefore} before? {dateBefore < theDate} ");

/* This example produces the following output:
 * 
 * Consider 10/21/2015
 *  Is 'theDate2' equal? True
 *  Is 4/21/2016 after? True
 *  Is 4/21/2016 before? False
 *  Is 10/11/2015 after? False
 *  Is 10/11/2015 before? True
*/
Dim theDate = DateOnly.ParseExact("21 Oct 2015", "dd MMM yyyy", CultureInfo.InvariantCulture) ' Custom format
Dim theDate2 = DateOnly.Parse("October 21, 2015", CultureInfo.InvariantCulture)
Dim dateLater = theDate.AddMonths(6)
Dim dateBefore = theDate.AddDays(-10)

Console.WriteLine($"Consider {theDate}...")
Console.WriteLine($" Is '{NameOf(theDate2)}' equal? {theDate = theDate2}")
Console.WriteLine($" Is {dateLater} after? {dateLater > theDate} ")
Console.WriteLine($" Is {dateLater} before? {dateLater < theDate} ")
Console.WriteLine($" Is {dateBefore} after? {dateBefore > theDate} ")
Console.WriteLine($" Is {dateBefore} before? {dateBefore < theDate} ")

' This example produces the following output
' 
' Consider 10/21/2015
'  Is 'theDate2' equal? True
'  Is 4/21/2016 after? True
'  Is 4/21/2016 before? False
'  Is 10/11/2015 after? False
'  Is 10/11/2015 before? True

「TimeOnly」結構

TimeOnly 結構代表一天時間值,例如每日鬧鐘或您每天吃午餐的時間。 TimeOnly 僅限於 00:00:00.0000000 - 23:59:59:59.9999999範圍,這是一天的特定時間。

在引進 TimeOnly 類型之前,程式設計人員通常會使用 DateTime 類型或 TimeSpan 類型來代表特定時間。 不過,使用這些結構來模擬沒有日期的時間,可能會造成一些問題,TimeOnly 解決:

  • TimeSpan 代表經過的時間,例如使用秒表測量的時間。 上限範圍超過 29,000 年,而且其值可以是負值,表示在時間中向後移動。 負 TimeSpan 不會指出一天中的特定時間。

  • 如果 TimeSpan 被用作一天中的時間,可能會被調整為超出24小時範圍的值。 TimeOnly 沒有這種風險。 例如,如果員工的工作班次從 18:00 開始,且持續 8 小時,則將 8 小時新增至 TimeOnly 結構會變換至 2:00

  • 使用 DateTime 表示一天中的某個時間,需要先將一個任意日期與這個時間關聯,然後再忽略該日期。 選擇 DateTime.MinValue (0001-01-01-01) 作為日期是常見的做法,不過,如果從 DateTime 值減去小時,可能會發生 OutOfRange 例外狀況。 TimeOnly 沒有這個問題,因為時間可以在24小時的時間範圍內前後調整。

  • 串行化 DateTime 結構包含日期元件,這可能會遮蔽數據的意圖。 此外,TimeOnly 序列化較少的數據量。

TimeOnly 範例

使用下列範例來瞭解 TimeOnly

將 DateTime 轉換為 TimeOnly

使用 TimeOnly.FromDateTime static 方法,從 TimeOnly 類型建立 DateTime 類型,如下列程式代碼所示:

var now = TimeOnly.FromDateTime(DateTime.Now);
Console.WriteLine($"It is {now} right now");

/* This example produces output similar to the following:
 * 
 * It is 2:01 PM right now
*/
Dim now = TimeOnly.FromDateTime(DateTime.Now)
Console.WriteLine($"It is {now} right now")

' This example produces output similar to the following
' 
' It is 2:01 PM right now

新增或減去時間

有三種方法可用來調整 TimeOnly 結構:AddHoursAddMinutesAddAddHoursAddMinutes 都採用整數參數,並據以調整值。 您可以使用負值來減法,使用正值來加法。 方法會傳回 TimeOnly 的新實例,因為結構是不可變的。 Add 方法會採用 TimeSpan 參數,並從 TimeOnly 值中新增或減去值。

因為 TimeOnly 只代表 24 小時的時間,所以在將提供給這三種方法的值相加時,會適當地向前或向後變換。 例如,如果您使用 01:30:00 值來表示上午 1:30,則從該期間新增 -4 小時,它會向後復原至 21:30:00,也就是下午 9:30。 AddHoursAddMinutesAdd 都有方法重載,可記錄變換的天數。

var theTime = new TimeOnly(7, 23, 11);

var hourLater = theTime.AddHours(1);
var minutesBefore = theTime.AddMinutes(-12);
var secondsAfter = theTime.Add(TimeSpan.FromSeconds(10));
var daysLater = theTime.Add(new TimeSpan(hours: 21, minutes: 200, seconds: 83), out int wrappedDays);
var daysBehind = theTime.AddHours(-222, out int wrappedDaysFromHours);

Console.WriteLine($"Time: {theTime}");
Console.WriteLine($" Hours later: {hourLater}");
Console.WriteLine($" Minutes before: {minutesBefore}");
Console.WriteLine($" Seconds after: {secondsAfter}");
Console.WriteLine($" {daysLater} is the time, which is {wrappedDays} days later");
Console.WriteLine($" {daysBehind} is the time, which is {wrappedDaysFromHours} days prior");

/* This example produces the following output:
 * 
 * Time: 7:23 AM
 *  Hours later: 8:23 AM
 *  Minutes before: 7:11 AM
 *  Seconds after: 7:23 AM
 *  7:44 AM is the time, which is 1 days later 
 *  1:23 AM is the time, which is -9 days prior
*/
Dim wrappedDays As Integer
Dim wrappedDaysFromHours As Integer

Dim theTime = New TimeOnly(7, 23, 11)

Dim hourLater = theTime.AddHours(1)
Dim minutesBefore = theTime.AddMinutes(-12)
Dim secondsAfter = theTime.Add(TimeSpan.FromSeconds(10))
Dim daysLater = theTime.Add(New TimeSpan(hours:=21, minutes:=200, seconds:=83), wrappedDays)
Dim daysBehind = theTime.AddHours(-222, wrappedDaysFromHours)

Console.WriteLine($"Time: {theTime}")
Console.WriteLine($" Hours later: {hourLater}")
Console.WriteLine($" Minutes before: {minutesBefore}")
Console.WriteLine($" Seconds after: {secondsAfter}")
Console.WriteLine($" {daysLater} is the time, which is {wrappedDays} days later")
Console.WriteLine($" {daysBehind} is the time, which is {wrappedDaysFromHours} days prior")

' This example produces the following output
' 
' Time: 7:23 AM
'  Hours later: 8:23 AM
'  Minutes before: 7:11 AM
'  Seconds after: 7:23 AM
'  7:44 AM is the time, which is 1 days later 
'  1:23 AM is the time, which is -9 days prior

剖析和格式化 TimeOnly

TimeOnly 可以從字串剖析,就像 DateTime 結構一樣。 所有標準 .NET 時間型剖析令牌都與 TimeOnly搭配運作。 將 TimeOnly 類型轉換成字串時,您也可以使用標準 .NET 日期格式設定模式。 如需格式化字串的詳細資訊,請參閱 標準日期和時間格式字串

var theTime = TimeOnly.ParseExact("5:00 pm", "h:mm tt", CultureInfo.InvariantCulture);  // Custom format
var theTime2 = TimeOnly.Parse("17:30:25", CultureInfo.InvariantCulture);

Console.WriteLine(theTime.ToString("o", CultureInfo.InvariantCulture));     // Round-trip pattern.
Console.WriteLine(theTime2.ToString("t", CultureInfo.InvariantCulture));    // Long time format
Console.WriteLine(theTime2.ToLongTimeString());

/* This example produces the following output:
 * 
 * 17:00:00.0000000
 * 17:30
 * 5:30:25 PM
*/
Dim theTime = TimeOnly.ParseExact("5:00 pm", "h:mm tt", CultureInfo.InvariantCulture) ' Custom format
Dim theTime2 = TimeOnly.Parse("17:30:25", CultureInfo.InvariantCulture)

Console.WriteLine(theTime.ToString("o", CultureInfo.InvariantCulture))     ' Round-trip pattern.
Console.WriteLine(theTime2.ToString("t", CultureInfo.InvariantCulture))    ' Long time format
Console.WriteLine(theTime2.ToLongTimeString())

' This example produces the following output
' 
' 17:00:00.0000000
' 17:30
' 5:30:25 PM

串行化 DateOnly 和 TimeOnly 類型

使用 .NET 7+,System.Text.Json 支援序列化和反序列化 DateOnlyTimeOnly 類型。 請考慮下列物件:

sealed file record Appointment(
    Guid Id,
    string Description,
    DateOnly Date,
    TimeOnly StartTime,
    TimeOnly EndTime);
Public NotInheritable Class Appointment
    Public Property Id As Guid
    Public Property Description As String
    Public Property DateValue As DateOnly?
    Public Property StartTime As TimeOnly?
    Public Property EndTime As TimeOnly?
End Class

下列範例將序列化一個 Appointment 物件,顯示產生的 JSON,然後將它反序列化回 Appointment 型別的新實例。 最後,將原始和新反序列化的實例進行相等性比較,並將結果輸出至主控台:

Appointment originalAppointment = new(
    Id: Guid.NewGuid(),
    Description: "Take dog to veterinarian.",
    Date: new DateOnly(2002, 1, 13),
    StartTime: new TimeOnly(5,15),
    EndTime: new TimeOnly(5, 45));
string serialized = JsonSerializer.Serialize(originalAppointment);

Console.WriteLine($"Resulting JSON: {serialized}");

Appointment deserializedAppointment =
    JsonSerializer.Deserialize<Appointment>(serialized)!;

bool valuesAreTheSame = originalAppointment == deserializedAppointment;
Console.WriteLine($"""
    Original record has the same values as the deserialized record: {valuesAreTheSame}
    """);
        Dim originalAppointment As New Appointment With {
            .Id = Guid.NewGuid(),
            .Description = "Take dog to veterinarian.",
            .DateValue = New DateOnly(2002, 1, 13),
            .StartTime = New TimeOnly(5, 3, 1),
            .EndTime = New TimeOnly(5, 3, 1)
}
        Dim serialized As String = JsonSerializer.Serialize(originalAppointment)

        Console.WriteLine($"Resulting JSON: {serialized}")

        Dim deserializedAppointment As Appointment =
            JsonSerializer.Deserialize(Of Appointment)(serialized)

        Dim valuesAreTheSame As Boolean =
            (originalAppointment.DateValue = deserializedAppointment.DateValue AndAlso
            originalAppointment.StartTime = deserializedAppointment.StartTime AndAlso
            originalAppointment.EndTime = deserializedAppointment.EndTime AndAlso
            originalAppointment.Id = deserializedAppointment.Id AndAlso
            originalAppointment.Description = deserializedAppointment.Description)

        Console.WriteLine(
            $"Original object has the same values as the deserialized object: {valuesAreTheSame}")

在上述程式代碼中:

  • Appointment 物件會具現化並指派給 appointment 變數。
  • appointment 實例會使用 JsonSerializer.Serialize序列化為 JSON。
  • 產生的 JSON 會寫入主控台。
  • 使用 Appointment將 JSON 反序列化回 JsonSerializer.Deserialize 型別的新實例。
  • 原始的實例與重新反序列化的實例會比對是否相等。
  • 比較的結果會寫入控制台。

如需詳細資訊,請參閱 如何在 .NET中序列化和反序列化 JSON。

使用 TimeSpan 和 DateTime

TimeOnly 可以從 TimeSpan建立,並轉換成 TimeSpan。 此外,TimeOnly 可以搭配 DateTime使用,以建立 TimeOnly 實例,或只要提供日期即可建立 DateTime 實例。

下列範例會從 TimeOnly建立 TimeSpan 對象,然後將它轉換回:

// TimeSpan must in the range of 00:00:00.0000000 to 23:59:59.9999999
var theTime = TimeOnly.FromTimeSpan(new TimeSpan(23, 59, 59));
var theTimeSpan = theTime.ToTimeSpan();

Console.WriteLine($"Variable '{nameof(theTime)}' is {theTime}");
Console.WriteLine($"Variable '{nameof(theTimeSpan)}' is {theTimeSpan}");

/* This example produces the following output:
 * 
 * Variable 'theTime' is 11:59 PM
 * Variable 'theTimeSpan' is 23:59:59
*/
' TimeSpan must in the range of 00:00:00.0000000 to 23:59:59.9999999
Dim theTime = TimeOnly.FromTimeSpan(New TimeSpan(23, 59, 59))
Dim theTimeSpan = theTime.ToTimeSpan()

Console.WriteLine($"Variable '{NameOf(theTime)}' is {theTime}")
Console.WriteLine($"Variable '{NameOf(theTimeSpan)}' is {theTimeSpan}")

' This example produces the following output
' 
' Variable 'theTime' is 11:59 PM
' Variable 'theTimeSpan' is 23:59:59

下列範例會從 DateTime 物件建立 TimeOnly,並選取任意日期:

var theTime = new TimeOnly(11, 25, 46);   // 11:25 AM and 46 seconds
var theDate = new DateOnly(2015, 10, 21); // October 21, 2015
var theDateTime = theDate.ToDateTime(theTime);
var reverseTime = TimeOnly.FromDateTime(theDateTime);

Console.WriteLine($"Date only is {theDate}");
Console.WriteLine($"Time only is {theTime}");
Console.WriteLine();
Console.WriteLine($"Combined to a DateTime type, the value is {theDateTime}");
Console.WriteLine($"Converted back from DateTime, the time is {reverseTime}");

/* This example produces the following output:
 * 
 * Date only is 10/21/2015
 * Time only is 11:25 AM
 * 
 * Combined to a DateTime type, the value is 10/21/2015 11:25:46 AM
 * Converted back from DateTime, the time is 11:25 AM
*/
Dim theTime = New TimeOnly(11, 25, 46) ' 11:   25 PM And 46 seconds
Dim theDate = New DateOnly(2015, 10, 21) ' October 21, 2015
Dim theDateTime = theDate.ToDateTime(theTime)
Dim reverseTime = TimeOnly.FromDateTime(theDateTime)

Console.WriteLine($"Date only is {theDate}")
Console.WriteLine($"Time only is {theTime}")
Console.WriteLine()
Console.WriteLine($"Combined to a DateTime type, the value is {theDateTime}")
Console.WriteLine($"Converted back from DateTime, the time is {reverseTime}")

' This example produces the following output
' 
' Date only is 10/21/2015
' Time only is 11:25 AM
' 
' Combined to a DateTime type, the value is 10/21/2015 11:25:46 AM
' Converted back from DateTime, the time is 11:25 AM

算術運算子和 TimeOnly 的比較

兩個 TimeOnly 實體可以彼此比較,而且您可以使用 IsBetween 方法來檢查時間是否介於另外兩個時間之間。 當加法或減法運算符用於 TimeOnly時,會傳回 TimeSpan,表示持續時間。

var start = new TimeOnly(10, 12, 01); // 10:12:01 AM
var end = new TimeOnly(14, 00, 53); // 02:00:53 PM

var outside = start.AddMinutes(-3);
var inside = start.AddMinutes(120);

Console.WriteLine($"Time starts at {start} and ends at {end}");
Console.WriteLine($" Is {outside} between the start and end? {outside.IsBetween(start, end)}");
Console.WriteLine($" Is {inside} between the start and end? {inside.IsBetween(start, end)}");
Console.WriteLine($" Is {start} less than {end}? {start < end}");
Console.WriteLine($" Is {start} greater than {end}? {start > end}");
Console.WriteLine($" Does {start} equal {end}? {start == end}");
Console.WriteLine($" The time between {start} and {end} is {end - start}");

/* This example produces the following output:
 * 
 * Time starts at 10:12 AM and ends at 2:00 PM
 *  Is 10:09 AM between the start and end? False
 *  Is 12:12 PM between the start and end? True
 *  Is 10:12 AM less than 2:00 PM? True
 *  Is 10:12 AM greater than 2:00 PM? False
 *  Does 10:12 AM equal 2:00 PM? False
 *  The time between 10:12 AM and 2:00 PM is 03:48:52
*/
Dim startDate = New TimeOnly(10, 12, 1) ' 10:12:01 AM
Dim endDate = New TimeOnly(14, 0, 53) ' 02:00:53 PM

Dim outside = startDate.AddMinutes(-3)
Dim inside = startDate.AddMinutes(120)

Console.WriteLine($"Time starts at {startDate} and ends at {endDate}")
Console.WriteLine($" Is {outside} between the start and end? {outside.IsBetween(startDate, endDate)}")
Console.WriteLine($" Is {inside} between the start and end? {inside.IsBetween(startDate, endDate)}")
Console.WriteLine($" Is {startDate} less than {endDate}? {startDate < endDate}")
Console.WriteLine($" Is {startDate} greater than {endDate}? {startDate > endDate}")
Console.WriteLine($" Does {startDate} equal {endDate}? {startDate = endDate}")
Console.WriteLine($" The time between {startDate} and {endDate} is {endDate - startDate}")

' This example produces the following output
' 
' Time starts at 10:12 AM And ends at 2:00 PM
'  Is 10:09 AM between the start And end? False
'  Is 12:12 PM between the start And end? True
'  Is 10:12 AM less than 2:00 PM? True
'  Is 10:12 AM greater than 2:00 PM? False
'  Does 10:12 AM equal 2:00 PM? False
'  The time between 10:12 AM and 2:00 PM is 03:48:52