Working with Dates And Times in Razor Pages Forms

When working with dates and times in a Razor Pages form, you need to render a suitable control based in the task requirement. Prior to HTML5, developers largely depended on third party date picker libraries. Now, a variety of native browser options exist, although they enjoy varied support across modern browsers. These include options for managing the date and time, just the date or time, and for working with the month or week of the year.

DateTime Inputs

In Razor Pages, the input tag helper renders an appropriate value for the type attribute based on the data type of the model property specified via the asp-for attribute.

[BindProperty]
public DateTime DateTime { get; set; }
DateTime: <input class="form-control" asp-for="DateTime" />

The default input type generated for DateTime properties is datetime-local in .NET Core 2.0 onwards, which is when Razor Pages was introduced. In ASP.NET Core 1.x and in MVC 5 and earlier, tag helpers and the strongly typed Html helpers would render the input type as datetime, but this was dropped from the HTML5 spec because it was never implemented by any of the browser vendors.

In Chrome, Edge and Opera, datetime-local renders a control that enables the user to select a date and time. The formatting of the appearance of the date and time in the control is decided by the locale settings of the underlying operating system, and the value itself is assumed to represent a local date and time as opposed to a universal time:

If you are using another browser (IE 11, Firefox, Safari), the control renders a plain input that behaves like a text input.

When examining the rendered mark up, you see that the value has been formatted by the input tag helper to a representation based on the ISO 8601 standard as specified in RFC3339:

DateTime-Local

This is the format that the HTML5 control requires. You should bear this in mind if you try to apply the value to the control yourself, e.g. via script. If you need to generate a suitably formatted value using .NET, you can use the "O" (or "o") format string, although you will need to set the Kind to Unspecified to ensure that the time zone offset is not included in the output because the datetime-local control doesn't support it:

var dt = new DateTime(DateTime.Now.Ticks, DateTimeKind.Unspecified);
var isoDateString = dt.ToString("O");

By default, the formatted string includes the time down to the millisecond, so the time picker part of the UI provides options to set hours, minutes, seconds and milliseconds:

DateTime in Razor Pages

More often, you will only want to enable the user to specify the time to the minute. You control this through the formatting of the time portion of the value passed to the control. You can do this in one of two ways. You can use the DisplayFormat data annotation attribute on the model property to specify the format and ensure that the format also applies when the value is in "edit mode" (a form control):

[BindProperty, DisplayFormat(DataFormatString = "{0:yyyy-MM-ddTHH:mm}", ApplyFormatInEditMode = true)]
public DateTime DateTime { get; set; }

Alternatively, you can use the asp-format attribute on the input tag helper itself:

DateTime: <input class="form-control" asp-for="DateTime"  asp-format="{0:yyyy-MM-ddTHH:mm}" />

Dates and times in Razor Pages

The default value for a DateTime in .NET is DateTime.MinValue, represented as 0001-01-01T00:00:00 in the control. If you don't want any value to appear initially, you can make the bound property nullable:

[BindProperty]
public DateTime? DateTime { get; set; }

Then the control will display its default settings:

Date And Time Inputs

To support a wider range of browsers using native controls as opposed to third party libraries, you can use separate date and time controls. A little more configuration is required in order to get the input tag helper to render the correct controls:

[BindProperty, DataType(DataType.Date)]
public DateTime Date { get; set; }
[BindProperty, DataType(DataType.Time)]
public DateTime Time { get; set; }

Both properties are DateTime types, but the DataType attribute is applied to them to set the correct type in the rendered input. The input tag helper supports both the DataType.Date and DataType.Time options and will render accordingly:

Once again, you can format the time by applying a format string to either a DisplayFormat attribute on the model property or via the asp-format attribute on the tag helper. When the values are posted, the model binder successfully constructs DateTime types with the time portion set to midnight in the case of the date input's value, and the date portion set to today in the case of the time input's value. You can combine the values to construct a new DateTime:

DateTime dt = Date.Add(Time.TimeOfDay);

Coordinated Universal Time

Coordinated Universal Time (or UTC) is recommended for use in applications that require dates and times to be stored or represented in a time zone agnostic fashion. For more details about this, you can read Rick Strahl's comprehensive post on the topic. None of the date or time controls support the ISO 8601 representation of a UTC time value e.g. yyyy-MM-ddTHH:mm:ssZ (where Z is the time zone information, representing zero time zone offset, and therefore identifies this value as a UTC time). However, you may have to work with applications where this standard format is used to exchange time information.

In .NET Core 3.0 applications or earlier, the model binder will successfully create a DateTime value from a valid ISO 8601 UTC time string, but it will generate a local time based on the settings of the server on which the application executes.

For example, take a value representing 02:15 on the morning of October 30th, 2020, UTC: 2020-10-30T02:15:00Z. The following image shows how the model binder parses this value when the server is set to Pacific Time Zone:

The default DateTimeModelBinder is able to recognise and process date and time strings that include time zone information. If the time zone is missing, the binder sets the Kind property of the resulting DateTime value to DateTimeKind.Unspecified. Other DateTimeKind values are Local (representing a local time) and Utc (a UTC time). Notice that the Kind in the image above is set to Local instead of Utc, despite the fact that this is clearly a UTC time, given the presence of the Z at the end of the string. The binder has converted the UTC time to a local time, based on the server settings. As I write this today (30th October) Pacific Daylight Time applies which is 7 hours prior to the UTC value i.e. last night. Tomorrow, when daylight saving ends on the west coast of the US, the generated value will be 8 hours prior to UTC, so the parsed value will represent a different time again. In order to get the UTC value, you need to either use the ToUniversalTime() method on the parsed result:

Or you can implement your own model binder to process UTC time strings globally within the application. I will explore custom model binders in my next article for a related task.

The good news is that from .NET 5, this has been resolved so that UTC times are handled correctly by the model binder without requiring any additional processing on the bound value:

None of the values have been adjusted, and the Kind is set to Utc automatically.

Month And Week Inputs

Month and week input types are implemented currently by Edge, Chrome and Opera. Where is it supported, the month type provides a means for the user to select a specific month and year:

The input tag helper will render a control with type="month" with a little configuration. This is achieved by using the DataType attribute overload that takes a string parameter representing a custom data type:

[BindPropertyDataType("month")] 
public DateTime Month { getset; }

The format of the input month's value is yyyy-MM. The input tag helper generates a suitable value successfully from a DateTime type. So you don't need to apply any format strings in order to get the month selector to render correctly:

<input class="form-control" asp-for="Month" />

When values are posted back, the default DateTimeModelBinder will bind this value to a DateTime, with the correct month and year, and the day set to 1.

The week input type will also render successfully just through setting a custom data type on a DateTime property:

[BindPropertyDataType("week")] 
public DateTime Week { getset; }

The valid format for the value is yyyy-Www, where the capital W is a literal "W" and ww represents the ISO 8601 week of the selected year. There is no format string in .NET for the week part of a DateTime, but the tag helper generates the correctly formatted value from a DateTime type successfully:


However, the default DateTimeModelBinder is unable to bind that value back to a DateTime. You have a number of options. The most crude option is to access the value directly from the Request.Form collection, parse it as a string and generate a DateTime yourself:

public void OnPost()
{
    var week = Request.Form["Week"].First().Split("-W");
    Week = ISOWeek.ToDateTime(Convert.ToInt32(week[0]), Convert.ToInt32(week[1]), DayOfWeek.Monday);
}

This example uses the ISOWeek utility class that was added in .NET Core 3.0. If you are working on a .NET Core 2 project, you can use the Calendar.GetWeekOfYear() method, but be aware that in some edge cases, it doesn't return the ISO 8601 week of the year.

You could also bind to a string instead of a DateTime. You would have to generate a properly formatted value, as well as parse the result:

[BindPropertyDataType("week")]
public string StringWeek { getset; }
 
public void OnGet()
{
    StringWeek = $"{DateTime.Now.Year}-W{ISOWeek.GetWeekOfYear(DateTime.Now)}";
}

Alternatively, you can implement a custom model binder or a type converter, both of which are preferable. I shall look at both of those options in forthcoming articles.

Summary

In the vast majority of cases, HTML5, the input tag helper and the default DateTimeModelBinder blend together to make working with dates and times easy in a Razor Pages form. Browser implementations of HTML5 inputs manage data in a standard way, while displaying it in a format that the user is familiar with, reducing the developers reliance on third party components.