- Get link
- X
- Other Apps
How to use the DateOnly and TimeOnly structures
The DateOnly and TimeOnly structures were introduced with .NET 6 and represent a specific date or time-of-day, respectively. Prior to .NET 6, and always in .NET Framework, developers used the DateTime type (or some other alternative) to represent one of the following:
- A whole date and time.
- A date, disregarding the time.
- A time, disregarding the date.
DateOnly
and TimeOnly
are types that represent those particular portions of a DateTime
type.
The DateOnly structure represents a specific date, without time. Since it has no time component, it represents a date from the start of the day to the end of the day. This structure is ideal for storing specific dates, such as a birth date, an anniversary date, or business-related dates.
Although you could use DateTime
while ignoring the time component, there are a few benefits to using DateOnly
over DateTime
:
The
DateTime
structure may roll into the previous or next day if it's offset by a time zone.DateOnly
can't be offset by a time zone, and it always represents the date that was set.Serializing a
DateTime
structure includes the time component, which may obscure the intent of the data. Also,DateOnly
serializes less data.When code interacts with a database, such as SQL Server, whole dates are generally stored as the
date
data type, which doesn't include a time.DateOnly
matches the database type better.
DateOnly
has a range from 0001-01-01 through 9999-12-31, just like DateTime
. You can specify a specific calendar in the DateOnly
constructor. However, a DateOnly
object always represents a date in the proleptic Gregorian calendar, regardless of which calendar was used to construct it. For example, you can build the date from a Hebrew calendar, but the date is converted to Gregorian:
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
*/
Use the following examples to learn about DateOnly
:
- Convert DateTime to DateOnly
- Add or subtract days, months, years
- Parse and format DateOnly
- Compare DateOnly
Use the DateOnly.FromDateTime static method to create a DateOnly
type from a DateTime
type, as demonstrated in the following code:
var today = DateOnly.FromDateTime(DateTime.Now);
Console.WriteLine($"Today is {today}");
/* This example produces output similar to the following:
*
* Today is 12/28/2022
*/
There are three methods used to adjust a DateOnly structure: AddDays, AddMonths, and AddYears. Each method takes an integer parameter, and increases the date by that measurement. If a negative number is provided, the date is decreased by that measurement. The methods return a new instance of DateOnly
, as the structure is immutable.
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
*/
DateOnly can be parsed from a string, just like the DateTime structure. All of the standard .NET date-based parsing tokens work with DateOnly
. When converting a DateOnly
type to a string, you can use standard .NET date-based formatting patterns too. For more information about formatting strings, see Standard date and time format strings.
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
*/
DateOnly can be compared with other instances. For example, you can check if a date is before or after another, or if a date today matches a specific date.
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
*/
The TimeOnly structure represents a time-of-day value, such as a daily alarm clock or what time you eat lunch each day. TimeOnly
is limited to the range of 00:00:00.0000000 - 23:59:59.9999999, a specific time of day.
Prior to the TimeOnly
type being introduced, programmers typically used either the DateTime type or the TimeSpan type to represent a specific time. However, using these structures to simulate a time without a date may introduce some problems, which TimeOnly
solves:
TimeSpan
represents elapsed time, such as time measured with a stopwatch. The upper range is more than 29,000 years, and its value can be negative to indicate moving backwards in time. A negativeTimeSpan
doesn't indicate a specific time of the day.If
TimeSpan
is used as a time of day, there's a risk that it could be manipulated to a value outside of the 24-hour day.TimeOnly
doesn't have this risk. For example, if an employee's work shift starts at 18:00 and lasts for 8 hours, adding 8 hours to theTimeOnly
structure rolls over to 2:00Using
DateTime
for a time of day requires that an arbitrary date be associated with the time, and then later disregarded. It's common practice to chooseDateTime.MinValue
(0001-01-01) as the date, however, if hours are subtracted from theDateTime
value, anOutOfRange
exception might occur.TimeOnly
doesn't have this problem as the time rolls forwards and backwards around the 24-hour timeframe.Serializing a
DateTime
structure includes the date component, which may obscure the intent of the data. Also,TimeOnly
serializes less data.
Use the following examples to learn about TimeOnly
:
- Convert DateTime to TimeOnly
- Add or subtract time
- Parse and format TimeOnly
- Work with TimeSpan and DateTime
- Arithmetic operators and comparing TimeOnly
Use the TimeOnly.FromDateTime static method to create a TimeOnly
type from a DateTime
type, as demonstrated in the following code:
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
*/
There are three methods used to adjust a TimeOnly structure: AddHours, AddMinutes, and Add. Both AddHours
and AddMinutes
take an integer parameter, and adjust the value accordingly. You can use a negative value to subtract and a positive value to add. The methods return a new instance of TimeOnly
is returned, as the structure is immutable. The Add
method takes a TimeSpan parameter and adds or subtracts the value from the TimeOnly
value.
Because TimeOnly
only represents a 24-hour period, it rolls over forwards or backwards appropriately when adding values supplied to those three methods. For example, if you use a value of 01:30:00
to represent 1:30 AM, then add -4 hours from that period, it rolls backwards to 21:30:00
, which is 9:30 PM. There are method overloads for AddHours
, AddMinutes
, and Add
that capture the number of days rolled over.
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
*/
TimeOnly can be parsed from a string, just like the DateTime structure. All of the standard .NET time-based parsing tokens work with TimeOnly
. When converting a TimeOnly
type to a string, you can use standard .NET date-based formatting patterns too. For more information about formatting strings, see Standard date and time format strings.
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
*/
With .NET 7+, System.Text.Json
supports serializing and deserializing DateOnly and TimeOnly types. Consider the following object:
sealed file record Appointment(
Guid Id,
string Description,
DateOnly Date,
TimeOnly StartTime,
TimeOnly EndTime);
The following example serializes an Appointment
object, displays the resulting JSON, and then deserializes it back into a new instance of the Appointment
type. Finally, the original and newly deserialized instances are compared for equality and the results are written to the console:
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}
""");
In the preceding code:
- An
Appointment
object is instantiated and assigned to theappointment
variable. - The
appointment
instance is serialized to JSON using JsonSerializer.Serialize. - The resulting JSON is written to the console.
- The JSON is deserialized back into a new instance of the
Appointment
type using JsonSerializer.Deserialize. - The original and newly deserialized instances are compared for equality.
- The result of the comparison is written to the console.
For more information, see How to serialize and deserialize JSON in .NET.
TimeOnly can be created from and converted to a TimeSpan. Also, TimeOnly
can be used with a DateTime, either to create the TimeOnly
instance, or to create a DateTime
instance as long as a date is provided.
The following example creates a TimeOnly
object from a TimeSpan
, and then converts it back:
// 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
*/
The following example creates a DateTime
from a TimeOnly
object, with an arbitrary date chosen:
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
*/
Two TimeOnly instances can be compared with one another, and you can use the IsBetween method to check if a time is between two other times. When an addition or subtraction operator is used on a TimeOnly
, a TimeSpan is returned, representing a duration of time.
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
*/
Comments