Wednesday, February 23, 2011

Client-side timezone detection and using it server-side in ASP.NET

First of all, this is pretty complicated and took me alot of time to figure out, even after reading alot of stuff on it. So here's the steps to get this to work

1. JavaScript for getting the timezone info
I recommend sticking these javascript functions in a .js file, i put them in ../js/TimeZone.js
function TimezoneDetect() {//see references for credits for this function
    var dtDate = new Date('1/1/' + (new Date()).getUTCFullYear());
    var intOffset = 10000; //set initial offset high so it is adjusted on the first attempt
    var intMonth;
    var intHoursUtc;
    var intHours;
    var intDaysMultiplyBy;

    //go through each month to find the lowest offset to account for DST
    for (intMonth = 0; intMonth < 12; intMonth++) {
        //go to the next month
        dtDate.setUTCMonth(dtDate.getUTCMonth() + 1);

        //To ignore daylight saving time look for the lowest offset.
        //Since, during DST, the clock moves forward, it'll be a bigger number.
        if (intOffset > (dtDate.getTimezoneOffset() * (-1))) {
            intOffset = (dtDate.getTimezoneOffset() * (-1));

    return parseFloat(intOffset / 60)
function setHiddenVal(hiddenId) {
    var hiddenObj = document.getElementById(hiddenId);
    hiddenObj.value = TimezoneDetect().toString();
 2. Call JavaScript from ASP.NET page
I figured the best way to do this is to stick the javascript call on my login page. So on the login page you need to add a HiddenField, Button, and a script tag.

Hidden Field:   <input runat='server' id="hiddenField" type="hidden" />

Button:   <asp:Button ID="btnLogin" runat="server"
        Text="Login" CssClass="button" OnClientClick="setHiddenVal('[hiddenField's ID]')" onclick="btnLogin_Click" />

Notice, the button has a server-side click event called btnLogin_Click, and a client-side on click event. In the on click event, you call setHiddenVal with the HiddenField's id as the parameter. You will need to find the assigned ID by loading the page and looking at the page source. If you are using master pages w/ content place holder's it will not be what you expect.

Script tag:  <script type="text/javascript" src="../js/TimeZone.js"></script>
As mentioned before ../js/TimeZone.js is where I put the javascript functions

3. Store the HiddenField value in the Session["UtcOffset"]
In the login button's server-side onclick event, btnLogin_Click, you can retrieve the HiddenField's value, and store it in the Session variable like this:
protected void btnLogin_Click(object sender, EventArgs e)
//auth code...

            Session["UtcOffset"] = hiddenField.Value;
//redirect to user's profile...
 4. Use Session["UtcOffset"] to calculate future UTC times and convert back to the user's timezone

First, how to convert a UTC DateTime to a DateTime in the user's timezone. Let's assume we have a function called getStartTimeUtcFromDatabase() which returns a UTC DateTime from the database. *Note: There's no error handling for clarity purposes

 //pull the UtcOffset which we saved in the Session in section 3 above
double utcOffset =  Convert.ToDouble(Session["UtcOffset"]); 

DateTime StartTime = getStartTimeUtcFromDatabase();
StartTime = StartTime.AddHours(utcOffset);

//For example, StartTime = 2/24/11 12:30 AM, and UtcOffset= -8 (PST)
//The end result would give us StartTime = 2/23/11 4:30 PM

Second, how to convert user input datetime to the equivalent UTC time. Let's assume we have already collected the date and time and concated them together into a DateTime object called userStartTime.
double utcOffset  = Convert.ToDouble(Session["UtcOffset"]) * -1;
userStartTime = userStartTime.AddHours(utcOffset);

//For example, user start time = 2/23/11 4:30 PM, and UtcOffset= -8 (PST)
//So because we are multiplying by -1, we are actually adding 8 to the datetime,
//this results in userStartTime = 2/24/11 12:30 AM

I hope this helps anyone who is trying to figure this out. It took me quite a bit of time to pull this all together.... . Here's the sources:

No comments:

Post a Comment