如何使用 DateOnly 和 TimeOnly 結構
DateOnly 和 TimeOnly 結構是使用 .NET 6 引進的,分別代表特定的日期或日期時間。 在 .NET 6 之前,且一律在 .NET Framework 中,開發人員會使用 DateTime 類型(或其他替代方式)來代表下列其中一項:
- 整個日期和時間。
- 日期,忽略時間。
- 僅考慮時間,不計日期。
DateOnly
和 TimeOnly
是代表 DateTime
類型特定部分的類型。
DateOnly 結構
DateOnly 結構代表一個不包括時間的特定日期。 因為它沒有時間元件,所以代表從一天開始到一天結束的日期。 此結構很適合用來儲存特定日期,例如出生日期、周年日期或商務相關日期。
雖然您可以在忽略時間元件時使用 DateTime
,但使用 DateOnly
比 DateTime
有幾個優點:
如果
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 結構:AddDays、AddMonths和 AddYears。 每個方法都會採用整數參數,並依該度量增加日期。 如果提供負數,日期會按該數值向前調整。 方法會傳回 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 結構:AddHours、AddMinutes和 Add。
AddHours
和 AddMinutes
都採用整數參數,並據以調整值。 您可以使用負值來減法,使用正值來加法。 方法會傳回 TimeOnly
的新實例,因為結構是不可變的。
Add
方法會採用 TimeSpan 參數,並從 TimeOnly
值中新增或減去值。
因為 TimeOnly
只代表 24 小時的時間,所以在將提供給這三種方法的值相加時,會適當地向前或向後變換。 例如,如果您使用 01:30:00
值來表示上午 1:30,則從該期間新增 -4 小時,它會向後復原至 21:30:00
,也就是下午 9:30。
AddHours
、AddMinutes
和 Add
都有方法重載,可記錄變換的天數。
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
支援序列化和反序列化 DateOnly 和 TimeOnly 類型。 請考慮下列物件:
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