如何使用 DateOnly 和 TimeOnly 結構
DateOnly 和 TimeOnly 結構隨 .NET 6 引進,分別代表特定的日期或當天時間。 在 .NET 6 之前,且一律在 .NET Framework 中,開發人員使用 DateTime 類型 (或某些其他替代) 來代表下列其中一項:
- 整個日期和時間。
- 日期,忽略時間。
- 時間,忽略日期。
DateOnly
和 TimeOnly
是代表 DateTime
類型的特定部分類型。
DateOnly 結構
DateOnly 結構代表不含時間的特定日期。 因為沒有時間元件,所以該結構代表從一天開始到一天結束的日期。 此結構非常適合用來儲存特定日期,例如生日、週年日或商務相關日期。
雖然您可以在忽略時間元件時使用 DateTime
,但對 DateTime
使用 DateOnly
有幾個優點:
如果結構是依時區時差,則
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 靜態方法從 DateTime
類型建立 DateOnly
類型,如下列程式碼所示:
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.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 靜態方法從 DateTime
類型建立 TimeOnly
類型,如下列程式碼所示:
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 會寫入主控台。
- JSON 會使用 JsonSerializer.Deserialize 還原序列化回
Appointment
類型的新執行個體。 - 系統會比較原始和新還原序列化的執行個體是否相等。
- 比較的結果會寫入主控台。
如需詳細資訊,請參閱如何在 .NET 中序列化和還原序列化 JSON。
搭配使用 TimeSpan 和 DateTime
TimeOnly 可以從其中建立,並轉換成 TimeSpan。 此外,TimeOnly
也可以搭配 DateTime 使用來建立 TimeOnly
執行個體,或在提供日期時可建立 DateTime
執行個體。
下列範例會從 TimeSpan
建立 TimeOnly
物件,然後將其轉換回:
// 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
下列範例會從 TimeOnly
物件建立 DateTime
,並選取任意日期:
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