World time zone for your ASP.NET site
How to use local time and make world time zone calculations in your ASP.NET applications
You can get the current code release here: ChronosTimeZone
Introduction
The problem to show the local time in a web application located on a web server somewhere else is well known. The first step is to store all your time data in UTC so you have an uniform independent view of your time data. So far so good but then we have to display this uniform time in local time depending on where our web user is. So what we basically need is functionality that converts an UTC to local time (and sometimes vice versa) based on some rules for the time zone that we require. In most current web frameworks, for example PHP, Perl and Java 2, there is already functionality for time conversions and the required time zone rules.
Timezone libraries
- tzphp - Small library for PHP
- Joda Time - Java date and time API
- DateTime Project - datetime modules for Perl
All the above projects use the Olson Timezone Database as a source of rules for transitions and offsets from UTC. This database is currently in use in many Unix operating systems and Mac OS X, so I can say it is the definitive source of time zone knowledge. Many contributors help maintain the rules up to date with the constant changes.
On the other hand with the introduction of .NET Framework 2.0, Microsoft has also found useful to add conversion functions to the DateTime structure and a new TimeZone class. These work with the normal time zone database found in the registry and only support conversions to the local zone the IIS server computer is in. And on top of that, there is no way to use any other, apart from reconfiguring the computer configuration. All this does not seem very helpful when you have many users in different parts of the world and different zones. And another drawback is the windows database contains rules only for the current year, you can not make accurate time conversions for dates in the past.
Alternatives
So what are the alternatives when you need to have time conversion functionality in your ASP.NET web application. I have found some commercial alternatives, but in the price range of $200 I was inclined to look for some free ones. After all, there is an excellent timezone library in the public domain and it is updated regularly.
I have found several free projects that can extract the windows time zone data and use it to perform the conversions. While these tend to do the work, they certainly require special access permissions for your web application, something that is not so easily granted when you host your application on a shared hosting provider.
Free ASP.NET timezone projects
- SimpleTimeZone - Michael Brumm's nice little class (implemented in VB)
- Time Zone Conversion Classes for Microsoft .NET 1.0 by CrankedUp.com (implemented in C#)
Another interesting solution is proposed by Darren Neimke in the form of a web service that you can access remotely from your web application. The source of the project is also accessible, so one can run her own service, or just point to the service provided from the author. While this approach sounded interesting, under further inversigation I found the service used again the windows zone database, accessing the required information from the windows registry. More on this project here Time Zone Web Service
Challenge
Well, what I really liked to have is an ASP.NET class that can use the Olson zoneinfo database for its time computations. After all, the database is public domain and is updated regularly and one can store it as part of the web application data, no special permissions are required to host your application anywhere, just remember to update the database when new version pops up. Sounds good, but while the database sources are text files and there is also an compiled binary version of the rules, .NET is not very suitable for binary file parsing, it is mostly made to work on with XML content. So if I can found this database in an XML? Sure, please welcome the The Chronos Time Zone Repository.
Chronos is an Date and Time computations library implementing the Olson zoneinfo functionality for the Smalltalk language. While this library compiles and uses the library in its own internal binary format, the developers have made a very useful addition to it, so they are able to generate an XML representation of it. While this XML is not used by Chronos, it is generously provided for other projects and needs, and this just happens to fill my particular need.
So now that I have all the data I want in a single 7MB XML file (and regular updates are also provided for free), I just need to put up some code that can extract the required info and do the time computations for whatever timezone I choose.
Implementation
Here are the details about my implementation and you can grab the source as well. I hope it can work for your needs too, or you can tweak it or fix something. Anyway, the code is released under the Creative Commons CC-GNU LGPL licence that also allows you to redistribute your works on it.
The implementation consists of two classes TimeZoneMaster and TimeZoneHelper. The TimeZoneMaster object loads the repository from XML file and contain it in memory in an XPathDocument. The XPathDocument object was chosen, because only read-only access to the actual XML is required and XPathDocument should render better performace in such cases. The class constructor initializes the object and reads the XML repository from disk location specified in the chronosTimeZoneRepository application settings property. If this property is not found, the location defaults to `~/App_Data/timeZoneRepository.xml'. The object works with the single XML file Chronos Repository. The loadFile method with a disk location argument is provided for loading new repository file after object creation. The getListOfZones method returns an ArrayList collection containing all zone names that the master supports. In this implementation the class does not support the zone aliases defined in the Chronos XML file. Finally you can use the getTimeZoneByName method with an zone name parameter to get an TimeZoneHelper object for the required zone.
The TimeZoneMaster object operates as a Singleton and provides no public constructor, but one can obtain a reference to the single instance by the Instance static member variable. The real zone conversion work is performed by the TimeZoneHelper objects you instantiate. For this reason, the TimeZoneMaster methods can be called from different application threads and should be generally thread safe.
The TimeZoneHelper object should only be obtained by getTimeZoneByName. This way the object is instantiated with the required zone rules to make the conversions. The functionality you require is available by the toLocalTime and toUniversalTime methods. Both methods receive a System.DateTime structure as parameter and return the converted time again in a System.DateTime structure. In current implementation of toLocalTime the three letter zone abbreviation is not returned to the caller as I don't really required this functionality. This can easily be extended so the method returns a structure with the System.DateTime as value member and the string abbreviation as abbr member if you require it.
Generally an TimeZoneHelper object can be created for each user and stored in the user session. Thus the TimeZoneHelper methods are not considered thread safe. The zone rules are stored by the TimeZoneHelper object in ArrayList collections of structures and it should be possible to store the object by a SQL server session provider, but I have not tested this.
While XPath expressions can be used for XML data parsing, I have made the parsing with a more traditional node traversing approach as the initial proof of concept I implemented used this. As an future enhancement I will probably consider moving to an XPath parsing approach.
You can get the current code release here: ChronosTimeZone.zip
Limitations
The TimeZoneMaster object does no process the aliases definitions in the Chronos XML repository and thus does not provide a way to find a zone rules by alias.
The TimeZoneHelper object parsing code uses a specific order for accessing the properties of DayOfMonth and WeekOfMonthDayOfWeek transition anual date types. While no changes to the XML schema are anticipated, this behaviour of the code is considered a limitation.
The TimeZoneHelper object parsing code relies on the order of annual transition definitions for an intraYearTransition declaration to build its own collection and then for searching the matching annual rules. In the current implementation of the XML repository the order is strictly from the earliest date to the latest transition date in the year. No changes to the XML schema are anticipated, but as a safe side, the code should implement some way to order the transitions in the collection accordingly.
The TimeZoneHelper object relies on annual transitions rules to be specified according to the Gregorian calendar. While the XML schema has a provision for using different calendars, in the current Chronos XML repository no other type of calendar is used and the code does not check the calendar properties.
The toUniversalTime method does not check if the specified date is valid based on the daylight savings change rules (when daylight savings goes into effect there is usually an 1 nonexistent hour in local time). Such time will be converted to an universal time before the daylight savings has come into effect.
The toUniversalTime method does no provide a way to distinguish overlapping time periods in the rules (when daylight saving ceases to effect there is usually an 1 hour overlap). Local times in the overlapping time period are considered to be with daylight saving in effect.
The toLocalTime method does not return the abbreviated zone name for the the returned time.