Tuesday, 8 November 2011

World Clocks with jClock and TimeZoneInfo

Everyone else seems to be writing about the clocks changing this week, I don't see why I should miss out...

Last year while I was working on our group intranet project, I wrote a web part for diplaying times around the world corresponding to some of our major offices. It uses the jClock jQuery plugin so it does actually tick every second instead of being static (and in a future release I'm going to change this so it doesn't show the seconds and only ticks every minute instead). The list of locations is driven by an XML file so we can dynamically add more if we open offices in other time zones.










jClock by default shows you your local time, or you can give it an offset (in decimal) from GMT/UTC if you want to display the time from a different timezone. So the XML file that we went into production with last year was along the lines of;
<locations>
    <location>
        <name>London</name>
        <offset>0</offset>
    </location>
    <location>
        <name>Stockholm</name>
        <offset>1.0</offset>
    </location>
</locations>

The serverside code in the web part then uses a HtmlTextWriter to generate a set of DIVs and a block of JavaScript that sets up jClock.

<script type="text/javascript">
    $(document).ready(function ()
    {
        $('#jclock').jclock();
        $('#jclockLondon').jclock({ utc: true, utc_offset: 0 });
        $('#jclockStockholm').jclock({ utc: true, utc_offset: 1 });
    });
</script>

<h2>WORLD CLOCKS</h2>
<div style="text-align: center;">
    <div>
        <span>Local time:</span>
        <span id="jclock"></span>
    </div>
    <div style='background-color: #F0F0F0; width: 100%;'>
        <span>London</span>
        <span id='jclockLondon'></span>
    </div>
    <div>
        <span>Stockholm</span>
        <span id='jclockStockholm'></span>
    </div>
</div>
The  two generated strings are put into a Pair and then cached as one object to cut down some of the work for the server. And all this was fine, until the clocks changed in the spring. I worked out the new offsets and updated the XML file on the server, so then we had;
<locations>
    <location>
        <name>London</name>
        <offset>1.0</offset>
    </location>
    <location>
        <name>Stockholm</name>
        <offset>2.0</offset>
    </location>
</locations>

When the clocks changed again last week, I realised that this was going to be unsustainable and needed reworking. My first thought was to add a set of dates to the XML that denoted when to switch between winter and summer times. I coded this up, tested it, and checked it in, thinking I was done. Until it was pointed out to me that not everyone changes their clocks on the same date. 

With a steer from Gustaf, I looked into the System.TimeZoneInfo class. Now I wish I'd looked at this last year...

To get an instance of the TimeZoneInfo class, you call the static method FindSystemTimeZoneById and pass it the string Id of the timezone you want e.g. 'GMT Standard Time'. You can see all the timezones that your system supports by calling GetSystemTimeZones, which returns a ReadOnlyCollection of all the timezones Windows knows about. Each timezone has an id but also a DisplayName e.g. ' - it's the DisplayName that you see if you go into the Control Panel to change your system's timezone:










The id is constant across all installations of Windows, however the Displayname is localised - this was an important point for me as our intranet runs on servers in Sweden. For our World Clocks web part, the key function of TimeZoneInfo is GetUTCOffset. This takes a DateTime parameter which allows you to calculate the UTC offset for any given timezone on any given date. The key point is that this automatically factors in daylight savings times e.g. British Summer Time. I'd expected British Summer Time (or equally Central European Summer Time) to be listed as separate timezones, but they aren't, the timezone knows when it should apply changes for daylight savings e.g.

TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Console.WriteLine(string.Format("Id: {0} - DisplayName: {1}",
info.Id,info.DisplayName));
Console.WriteLine(string.Format("Summer offset is {0}"
info.GetUtcOffset(new DateTime(2011,5,7))));
Console.WriteLine(string.Format("Winter offset is {0}"
info.GetUtcOffset(new DateTime(2011,11,7))));
Console.ReadLine();

produces

One of my colleagues asked what will happen if the rules for a given timezone change e.g. suppose the UK changes the dates on which the clocks change. The answer is this is now Microsoft's problem instead of mine, all the timezone information is held in the Registry and if any timezone changes, Microsoft will release a Windows Update that contains the new information.

So I've got rid of an annoying manual job in changing the XML twice a year, and the XML itself is much simpler because now for each location all it needs is a name, and a timezone id. Simples.

No comments: