Dates and times suck. Plain and simple. One thing that's not obvious, but you need to be keenly aware of is that a concept like "local time" is highly variant. When you do something like TimeOut.LocalTime
, you're talking about local server time, which could be, and likely is, vastly different from the user's local time. First and foremost, you need to define which you're looking for, and more likely than not, it's the user's time, not the server's, that is of interest. As a result LocalTime
is useless to you.
If you care about the user's local time, then you have to get them to provide it to you. There is no way for the server to discern this information. You can either provide time zone drop downs next to each of these date and time fields or, if the user is authenticated, you could have them set it on their profile, and then use that for everything they submit.
Either way, the other issue you'll have is in allowing user input of dates and times. When you're transferring data between a client and server, you're dealing with strings. The modelbinder in MVC helpfully parses these string values to bind them to more appropriate types like DateTimeOffset
, but in order for that to work, the posted value must be in a very specific format, or that conversion process will fail. Expecting a user to do something in a very specific way is of course a recipe for disaster, so you need to somehow present them with an easy to use interface that still gets you the information you need, without providing the user any opportunity to screw it up, because given an opportunity, a user will screw it up.
For that reason, your best friend is the HTML5 input types, specific date
and time
. There's others like datetime
, datetime-local
, etc., but these are not supported in any current browser and actually in danger of being deprecated by the HTML5 spec. Pretty much every modern browser supports some form of "date" control and "time" control (separately) now, and even if it's not supported, it's still easier for a user to enter discrete units like a "date" value and a "time" value correctly than one combined "datetime" value.
Based on all of this, I'd recommend you change your view model in the following way:
[DataType(DataType.Time)]
public TimeSpan TimeIn { get; set; }
[DataType(DataType.Date)]
public DateTime DateIn { get; set; }
public string TimeZoneIn { get; set; }
[DataType(DataType.Time)]
public TimeSpan TimeOut { get; set; }
[DataType(DataType.Date)]
public DateTime DateOut { get; set; }
public string TimeZoneOut { get; set; }
Then, you can add some helper properties like:
public DateTimeOffset InUtc =>
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateIn, TimeZoneIn).Add(TimeIn).ToUniversalTime();
public DateTimeOffset OutUtc =>
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateOut, TimeZoneOut).Add(TimeOut).ToUniversalTime();
One other thing. Although you're storing in UTC (and perhaps especially because you're storing in UTC), you still need to persist at least the time zone portion, so you can convert back into that time zone later. It should also be noted that if you're using SQL Server as your persistence store, DateTimeOffset
will translate to a DATETIMEOFFSET
column type, which can encode the time zone portion. In that scenario, there's no need to convert to UTC as you can always get to whatever time zone you need whether it's stored as the user's local time, UTC, or something else entirely. However, any other database will need UTC datetimes only, so it does somewhat pigeon hole you in.