views:

371

answers:

4

I have a DateTime stored in UTC time that I'd like to display to the user in their local time from within a GridView control. How can I convert my DateTime to the user's time (not my server's local time)? Here is my current field as it appears in the GridView Columns collection:

<asp:BoundField DataField="RunTime" HeaderText="Run Time"
  SortExpression="RunTime" DataFormatString="{0:f}" />
+3  A: 

You would need to know the user's time zone - for example, from profile or user data your website keeps. Alternatively, you can try to guestimate based on GeoIP data, although that can be suspect because the user may not be using that particular time zone on their computer.

.NET does not automatically know what time the user is using on the remote end.

Tejs
+2  A: 

you best bet would be to store the user's preference of time zone. You can't correctly guess the time zone from the browser info.

here's a quick tutorial to store the selected timezone in Session, and create a quick control that can be on every page to update the printed dates.

http://www.dotnet-friends.com/articles/asp/artinasp381efae4-87c8-41ae-9427-2ae75edd9594.aspx

Glennular
+2  A: 

You can use JavaScript to find the user's timezone:

var userTime =(new Date().getTimezoneOffset()/60)*(-1);

Then, store this in a hidden field or pass it along as a parameter. In your page, you'll have to override the GridView's RowDataBound event, find the time and convert it using DateTime's conversion methods.

edit: Probably the safest way to do this is to have the user store their timezone in a profile. That way, turning off JavaScript won't affect your application.

edit: Server-side code

    static void Main()
    {            
        const string timeFmt = "{0,-30}{1:yyyy-MM-dd HH:mm}";
        DateTime dt = DateTime.Now.ToUniversalTime();
        for (int i = -12; i <= 12; i++)
        {
            DateTime converted = ConvertToLocalDateTime(dt, i);
            Console.WriteLine(timeFmt, "Offset: " + i , converted);
        }
        Console.ReadLine();
    }

    static DateTime ConvertToLocalDateTime(DateTime dateTime, int offset)
    {
         TimeZoneInfo destinationTimeZone = TimeZoneInfo.GetSystemTimeZones()
            .Where(x => x.BaseUtcOffset.Hours.Equals(offset)).FirstOrDefault();

        var rule = destinationTimeZone.GetAdjustmentRules().Where(x =>
            x.DateStart <= dateTime && dateTime <= x.DateEnd)
            .FirstOrDefault();

        TimeSpan baseOffset = TimeSpan.Zero;
        if(rule != null)
        {
            baseOffset -= destinationTimeZone.IsDaylightSavingTime(dateTime) ? 
                rule.DaylightDelta : TimeSpan.Zero;
        }

        DateTimeOffset dto = DateTimeOffset.Parse(dateTime.ToString());
        return new DateTime(TimeZoneInfo.ConvertTimeFromUtc(dateTime, destinationTimeZone).Ticks + baseOffset.Ticks);
    }

See: http://msdn.microsoft.com/en-us/library/system.timezone(VS.90).aspx

Jim Schubert
I want to do something similar to this, but instead of using the current date to get the offset (and incorrectly computing some dates), I need to use RunTime to get the offset. How can I do this from within a GridView?
Rachel Martin
You said RunTime was a UTC date. You need to get the offset from the user's current date (via JavaScript) first, then adjust RunTime with that new offset. .NET has no way of knowing client timezone information as everything runs server-side. Example: RunTime is 15:00 UTC. JS gets an offset of -6 for the client. RunTime needs to subtract 6 hours in order to display the time in the client's timezone (9:00).
subkamran
And how do you compensate when the date is last January? My current offset would be -5 for daylight saving time, and that would make my January time off by an hour.
Rachel Martin
added to my answer with an example from MSDN
Jim Schubert
I'm afraid this server-side code does not know what the user's timezone is. It will only use the server's time, and therefore, is not useful in my case.
Rachel Martin
That's why you use the client-side code to get the offset, store it in a hidden field, and pull that offset in for use on the server side. The server side code shows you how to calculate whether or not it is currently Daylight Saving Time, so you can make your adjustments accordingly.
Jim Schubert
I'll try to be clearer. The localZone variable contains a time zone on machine A. The offset is calculated on machine B. If the time zone on machine A does not observe the same Daylight Saving Time rules as the time zone on machine B, this script is useless. Well, that is the case here. Machine B is in a different time zone and could even be in the opposite hemisphere, where Daylight Saving Time is in January instead of July. In that case, the server calculation means nothing to the user.
Rachel Martin
I modified my answer to include a method that can be reused. It is important to note that when you run the javascript from my original answer, it returns the daylight saving offset. This is also taking into account rules for previous years. However, I'm not sure whether it should be `+ baseOffset.Ticks` or `- baseOffset.Ticks` at the end of the code.
Jim Schubert
Your server-side code is very inventive, but since it guesses at the timezone, I could end up in the southern hemisphere when I should be in the northern hemisphere, so I'm afraid even this doesn't get me the correct times.
Rachel Martin
+3  A: 

For my site I just used jQuery to do an AJAX request to a timezone HTTP handler that would record the timezone in session, then I created an extension method "ToVisitorTime" that would use this value.

In your base template or page header:

<script type="text/javascript" src="/lib/js/jquery.js"></script>

<asp:Placeholder id="plcTimezoneScript" Visible="false" runat="server">
    <script type="text/javascript">
        function getVisitorTimezone()
        {
            // get visitor timezone offset
            var curDt = new Date();
            $.post("/ajax/TimezoneOffset.ashx", {
                offset: -(curDt.getTimezoneOffset()/60)
            });
        }

        $(getVisitorTimezone);
    </script>  
</asp:Placeholder>

Then to hide it if you already grabbed the session, in the codebehind:

protected void Page_Load(object sender, EventArgs e)
{
    object sessOffset = Session["OFFSET"];

    if (sessOffset == null)
    {
        plcTimezoneScript.Visible = true;
    }
}

For the /ajax/TimezoneOffset.ashx handler:

using System;
using System.Web;
using System.Web.SessionState;

public class TimezoneOffset : IHttpHandler, IRequiresSessionState
{

    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/plain";
        object sessOffset = context.Session["OFFSET"];

        if (context.Request.RequestType == "POST")
        {
            if (sessOffset == null)
            {
                string offset = context.Request.Form.Get("offset");
                int Offset = 0;

                if (!String.IsNullOrEmpty(offset) 
                    && Int32.TryParse(offset, out Offset))
                {
                    context.Session.Add("OFFSET", Offset);
                }
            }
        }
    }

    public bool IsReusable {
        get {
            return false;
        }
    }

}

Then an extension method to take care of showing the right time (returns a DateTime object for further processing if need be). Remember to store this in a static class:

    public static DateTime ToVisitorTime(this DateTime UtcDate)
{
    // use timezone offset, if set
    object TimezoneOffset = HttpContext.Current.Session["OFFSET"];

    if (TimezoneOffset != null)
    {
        int Offset = 0;

        if (Int32.TryParse(TimezoneOffset.ToString(), out Offset))
        {
            UtcDate = UtcDate.AddHours(Offset);
        }
        else
        {
            UtcDate = UtcDate.ToLocalTime();
        }
    }
    else
    {
        // well, at least show the local server time
        UtcDate = UtcDate.ToLocalTime();
    }

    return UtcDate;
}

This was taken from a tutorial I've yet to publish but should do what you need. One downside is that on the first page load, nothing will be in the correct time.

You should use a JS method probably, considering this already requires JS to work. I would recommend the Datejs plugin and use some jQuery to automatically replace utc dates.

subkamran
To get a correct translation, I need to get each offset separately using RunTime instead of the current date. Your script will only correctly translate dates during my current portion of Daylight Saving Time.
Rachel Martin
Actually, the script will work fine. You need to change the BoundField into a TemplateField and adjust the value of RunTime:<%#Convert.ToDateTime(Eval("RunTime")).ToVisitorTime().ToString("f")%>The JS only retrieves the user's time zone offset, it does not modify any dates. ToVisitorTime assumes you are passing a UTC date.
subkamran
This script will fail if I run it today (April 29) on a date in January, which was not observing Daylight Saving Time. It will use the offset for today, which is one hour more than the offset I would need for January. Maybe I could run a script from within the TemplateField, but I don't know if that's possible.
Rachel Martin
excellent use of a handler, subkamran
Jim Schubert