0

I am creating a 'Time Clock' web app. However, I am having trouble getting the EditorFor to display the time in LocalTime. I have everything saved to the Database as UTC(via DateTimeOffset), as per best practice.

Any suggestions on how to get this to display in local time, and still save as UTC?

ViewModel

    [DataType(DataType.Time)]
    [DisplayFormat(DataFormatString = "{HH:mm}")]
    public DateTimeOffset TimeIn { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{MM, DD, YY}")]
    public DateTimeOffset DateIn { get; set; }

    [DataType(DataType.Time)]
    [DisplayFormat(DataFormatString = "{HH:mm}")]
    public DateTimeOffset TimeOut { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{MM, DD, YY}")]
    public DateTimeOffset DateOut { get; set; }

View

  <div class="form-group">
        @Html.LabelFor(model => model.TimeOut, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.TimeOut, new { htmlAttributes = new { @class = "form-control" } })
            @Html.EditorFor(model => model.DateOut, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.TimeOut, "", new { @class = "text-danger" })
        </div>
    </div>

Any help would be appreciated, please let me know if there is anything else you would like to see to help solve the issue. Thanks!

  • You'll probably need to make your own custom editor template: https://stackoverflow.com/a/21865067/1043380 – gunr2171 Sep 28 '17 at 18:03
  • I think I ran across that post. Do you think that would allow me to display my TimeIn EditorFor in local time? I wasn't positive that would allow me to display the time in local time. – Devan Peetz Sep 28 '17 at 18:13
  • I think I will go ahead and try the customer editor, I will let you know if that solves. Thanks! – Devan Peetz Sep 28 '17 at 18:15
  • Another alternative is to make another property in your model: `public DateTime LocalTimeOut { get => TimeOut.LocalTime; set => ...set TimeOut... }` and make an EditorFor for that. – gunr2171 Sep 28 '17 at 18:19

2 Answers2

1

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.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
-1

From another project, I spit out the UTC time from C#, and let Javascript convert it to local time from the timezone information it has from the browser/operating system.

So, to spit out a format that JS will read, I output the time using this in C#:

DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss") + " UTC")

Which gives you: 2017/09/28 18:40:22 UTC

And then, feed it into Javascript:

var date = new Date('2017/09/28 18:40:22 UTC');

date.toLocaleString({}, {
                weekday: "short", year: "numeric", month: "short",
                day: "numeric", hour : "numeric", minute : "numeric"
            });

This spits out: Thu, Sep 28, 2017, 2:40 PM

You can customize the JS output to your needs as well. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString

To save, you may probably have to pass on the timezone to the server, maybe in a hiddenfield so you can convert it to UTC in the Action before saving to database.

Jason Roner
  • 865
  • 1
  • 9
  • 14
  • This answer, and the answer above are exactly what I was looking for. I thought there may be a way to convert it on the front end via JS, but being the novice that I am, I lacked the knowledge to do so. Thank you! – Devan Peetz Sep 28 '17 at 19:03
  • The only thing with a JS solution is that you need to be mindful of display values vs. post values. Unfortunately, HTML provides no mechanism to say you want a form field to display one way while posting in another way, so you'll want to hook into the submit event of the form and convert all your datetimes back to UTC before actually posting. Otherwise, you'll be posting local datetimes that the server will interpret as UTC datetimes. – Chris Pratt Sep 28 '17 at 19:15