Thursday, January 5, 2012

Adding Time Zone Support to Dynamic Data

I love the ASP.NET Dynamic Data architecture. It really makes developing applications so much easier. It is quite easy to extend once you get used to how it all works. I always store my dates/times as UTC in the database. This means by default that is what users will see when the Dynamic Data application displays the date/time. Most people don’t think about UTC, so it is of little meaning to most people. In an ideal world all date/time fields would be displayed in the current users time zone of choice.

This can be done quite easily actually when you are using Dynamic Data. The reason is that all the date/time fields are displayed and edited using a common user controls that you have in your project. In particular if you open your ASP.NET Dynamic Data web site / project you will see there is a DynamicData directory and then a FieldTemplates directory. Here you will find two user controls: DateTime.ascx and DateTime_Edit. These are the controls that are used to display  and edit respectively all data/time data in your DynamicData application. So, all we have to do is convert UTC to desired timezone before it is displayed, and on save we need to convert desired timezone to UTC. That way the user will see the date/time in a time zone they understand, but the date/time is stored in UTC in the database.

Enhancing DateTime.ascx

The key to extending the DateTime.ascx control is knowing where to hook into the architecture of Dynamic Data Field Templates. The answer for this control is to override the FieldValue property.

 public override object FieldValue
        {
            get
            {
                var dateUI =  Convert.ToDateTime(base.FieldValue).GetDateTimeForUI();
                return dateUI;
            }
            set
            {
                base.FieldValue = value;
            }
        }

That is all that is required. I’ll show the meat of the GetDateTimeForUI() extension below. Alternatively, you could do your own time zone conversion here. The key is that this is where you convert the base.FieldValue (which is UTC) to the time zone you desire.

Enhancing DateTime_Edit.ascx

To extending the DateTime_Edit.ascx control the key is again knowing where to hook into the architecture of Dynamic Data Field Templates. The answer for this control is to override the DataBind() method and the ConvertEditedValue() method.

public override void DataBind()
{
    base.DataBind();
 
    if (Mode == DataBoundControlMode.Edit && FieldValue != null)
    {
        TextBox1.Text = DateTime.Parse(FieldValueEditString).GetDateTimeForUI().ToString();
    }
}
 
protected override object ConvertEditedValue(string value)
{
    string valueToSave = DateTime.Parse(value).GetUtcDateTime().ToString();
 
    return base.ConvertEditedValue(valueToSave);
}

That is all that is required. I’ll show the meat of the GetUtcDateTime() extension below. Alternatively, you could do your own time zone conversion here. The key is that in the DataBind() method you convert the FieldValueEditString (which is in UTC) to the time zone you desire, and that in the ConvertEditedValue() method you convert value (which is in the desired time zone) to UTC.

Doing the actual Timezone Conversion

I implemented the conversion methods as an extensions to the DateTime class. This is just for ease of use, but you can do this however you see best. In .NET 3.5 Microsoft added a very nice class called TimeZoneInfo. It makes converting between time zones trivial. The best thing is that it takes Day light savings into consideration when it does the conversion. Below is my implementation for displaying date/times in Pacific Standard Time (PST). For simplicity and limiting scope,  in this case I have hard coded the desired timezone, but you could pull it from the Profile of the current user if you collect that info from the user in some UI.

namespace MyApp.Helpers
{
    public static class DateTimeExtensions
    {
        // call when displaying value from db to the ui
        public static DateTime GetDateTimeForUI(this DateTime dateAsUtc)
        {
            DateTime dateForUI = TimeZoneInfo.ConvertTime(dateAsUtc, TimeZoneInfo.Utc, TimeZoneInfo.FindSystemTimeZoneById(Config.DefaultTimeZone));
            return dateForUI;
        }

        // call when saving value to db from the ui
        public static DateTime GetUtcDateTime(this DateTime dateFromUI)
        {
            DateTime dateAsUtc = TimeZoneInfo.ConvertTime(dateFromUI, TimeZoneInfo.FindSystemTimeZoneById(Config.DefaultTimeZone), TimeZoneInfo.Utc);
            return dateAsUtc;
        }
    }
}

No comments: