Friday, March 9, 2012

Calculating DateTime in different TimeZones

Problem:
In a web request, you know the location where the user is coming from, and you have to figure out the current time in the users timezone.

Notes:
To keep this example simple, I've made an assumption that we'll be calculating times only in US time zones. You can also use IP geocoding to figure out the location where the request is coming from and modify the code in this example accordingly.

Solution:
.net framework base class library provides for System.TimeZoneInfo class that can be used for doing timezone based calculations. The idea is as follows:
1. Convert the given time to a Utctime
2. Figure out users location. This can be done by
             a. Geocoding the IP
             b. If the users location is already known (through some profile data already available in the system)
3. Figure out the timezone applicable as per the users location
4. Convert UTC time from step 1 above to the time in users timezone by using TimezoneInfo.ConvertTimeFromUtc method.
The ConvertTimeFromUtc method is intelligent enough to apply daylight savings if applicable to the converted time.

Sample Code:

public class TimezoneComputation
{
          public TimezoneComputation(){}
       
        public virtual DateTime ConvertDateTimeToLocalDateTime(DateTime input,
                                                                                                    string timezoneShortName)
         {
                if (string.IsNullOrEmpty(timezone) == false)
                {
                    var tz = TimeZoneInfo.FindSystemTimeZoneById( timezoneShortName .ToFullTimeZoneName());
                    var tempDt = TimeZoneInfo.ConvertTimeToUtc(input);
                    return TimeZoneInfo.ConvertTimeFromUtc(tempDt, tz);
                }
                //by default return the input without any conversions
                return input;                  
         }
}
    //this helper class adds an extension method to string which returns the full timezone name
   //that can be used by the TimeZoneInfo.FindSystemTimeZoneById method
   //right now this has only the US time zones, but can be extended to include other timezones also

    internal static class TimeZoneMapper
    {
        public static string ToFullTimeZoneName(this string shortName)
        {
            switch (shortName.ToLower())
            {
                case "cst":
                    return "Central Standard Time";
                case "pst":
                    return "Pacific Standard Time";
                case "akst":
                    return "Alaskan Standard Time";
                case "mst":
                    return "Mountain Standard Time";
                case "est":
                    return "Eastern Standard Time";
                case "hst":
                    return "Hawaiian Standard Time";
                case "asst":
                    return "Samoa Standard Time";
                case "ast":
                    return "Atlantic Standard Time";
            }

            return "";
        }
    }

Further Remarks:
In the TimeZoneMapper helper class above, I return the long names (system identifiers) of different timezones. You can find this list here.

Let me know if you want to see how I figured out what to pass in the timezoneShortName parameter of TimezoneComputation.ConvertDateTimeToLocalDateTime method.

The sample code shown above does need some more error checking, for example for edge cases when input is mindatetime, which can be easily implemented.

Here is a sample unit test class (I am using nUnit here):

    [TestFixture]
    public class TimezoneComputationTests
    {

        #region ConvertDateTimeToLocalDateTime Tests

        [Test]
        public void ConvertDateTimeToLocalDateTimeReturnsTheCorrectTimeByTimeZoneShortName()
        {
            //check EST. 2/29/2012 11:10:00 CST should be 2/29/2012 12:10:00 EST
            DateTime cstTime = new DateTime(2012, 2, 29, 11, 10, 0);
            DateTime dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "est");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(12, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check CST. 2/29/2012 11:10:00 CST should be 2/29/2012 11:10:00 CST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "cst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check PST. 2/29/2012 11:10:00 CST should be 2/29/2012 9:10:00 PST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "pst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(9, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check MST. 2/29/2012 11:10:00 CST should be 2/29/2012 10:10:00 MST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "mst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(10, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check AKST. 2/29/2012 11:10:00 CST should be 2/29/2012 8:10:00 AKST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "akst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(8, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check HST. 2/29/2012 11:10:00 CST should be 2/29/2012 7:10:00 HST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "hst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(7, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);


            //check empty timezoneshortname. Should return back the input unmodified
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check null timezoneshortname. Should return back the input unmodified
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, null);
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);
        }

        #endregion

    }


Make Custom Gifts at CafePress

No comments:

Post a Comment