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:
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:
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}" />
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:
[BindProperty, DataType("month")] public DateTime Month { get; set; }
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:
[BindProperty, DataType("week")] public DateTime Week { get; set; }
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:
[BindProperty, DataType("week")] public string StringWeek { get; set; } 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.