sebnilsson.com | Liquid Development Is What I Do
Seb Nilsson

Display Local DateTime with Moment.js in ASP.NET

Moment.js

Displaying a DateTime in local format in C# is relatively easy, but it will only use the server's settings to tell what "local" is.

For example, you might want 2016-03-07 14:35 UTC to show as 2016-03-07 15:35 for a visitor from a CET-timezone.

If you want to dynamically show the local date and time you can use the web-client's information through JavaScript and format it with Moment.js, for any user, anywhere in the world.

To do this in a way that is fault-tolerant and also SEO-friendly I want the UTC-DateTime to be hard-coded in the HTML and let Moment.js format it on the fly, when the page loads. To do this I need to populate my .cshtml-file with the following:

<span class="local-datetime"
        title="@(Model.DateUtc.ToString("yyyy-MM-dd HH:mm")) UTC"
        data-utc="@(Model.DateUtc.GetEpochTicks())">
    @(Model.DateUtc.ToString("yyyy-MM-dd HH:mm")) UTC
</span>

Make sure you run .ToUniversalTime() on your DateTime first.

Notice the .GetEpochTicks()-extension method. It makes sure the format of the DateTime is passed in a format that Moment.js can handle easily. The implementation looks like this:

private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

public static double GetEpochTicks(this DateTime dateTime)
{
    return dateTime.Subtract(Epoch).TotalMilliseconds;
}

The last step is to tell Moment.js to format our DateTime to a local format:

$('.local-datetime').each(function() {
    var $this = $(this), utcDate = parseInt($this.attr('data-utc'), 10) || 0;

    if (!utcDate) {
        return;
    }

    var local = moment.utc(utcDate).local();
    var formattedDate = local.format('YYYY-MM-DD HH:mm');
    $this.text(formattedDate);
});

If this (or other, unrelated) JavaScript-code would fail for any reason the UTC-DateTime is the actually HTML-content and will still be displayed.

Serialize HtmlString & MvcHtmlString in JSON.NET

JSON

The HtmlString-class (and MvcHtmlString) that is and has been used in the ASP.NET-platform, including WebPages, since the introduction of ASP.NET MVC is basically just a wrapped string, that doesn't gets automatically HTML-encoded when used in Razor-views. Despite this fact, if you want to serialize or deserialize this object in JSON.NET it will come back as null.

To be able to serialize an object containing a property of a type inheriting from IHtmlString, like HtmlString and MvcHtmlString, to then, for instance, cache the serialized object, you need to implement your own Newtonsoft.Json.JsonConverter that handles the serialization and deserialization.

HtmlStringConverter : Newtonsoft.Json.JsonConverter

public override bool CanConvert(Type objectType)
{
    return typeof(IHtmlString).IsAssignableFrom(objectType);
}

public override object ReadJson(
    JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var value = reader.Value as string;
    // Specifically MvcHtmlString
    if (objectType == typeof(MvcHtmlString))
    {
        return new MvcHtmlString(value);
    }
    // Generally HtmlString
    if (objectType == typeof(HtmlString))
    {
        return new HtmlString(value);
    }

    // Fallback for other (future?) implementations of IHtmlString
    return Activator.CreateInstance(objectType, value);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    var htmlString = value as HtmlString;
    if (htmlString == null)
    {
        return;
    }

    writer.WriteValue(htmlString.ToString());
}

You then have to register this Converter in your JsonSerializerSettings like this:

CurrentConverter.Converters.Add(new HtmlStringConverter());