Introduction
Imagine you are developing a Web or desktop application where you need to work with DateTime
values in a time zone specified by the user's preference. I got myself into such a situation several weeks ago and I started to search the Web for a suitable solution. Unfortunately I didn't find anything I liked, so I started to design my own solution.
The first article I took into consideration was Mike Dimmick's WorldClock article here on The Code Project. If you need to convert DateTime
values from/to various time zones, this could be an acceptable solution. But it was not my case exactly because I just was to convert times from/to UTC values, so I came to the conclusion that I needed to extend the abstract System.TimeZone
class provided by the .NET Framework. Furthermore, Mike's code doesn't really comply with my sense of object oriented design, no offence.
You may also find it useful to use the Olson time zone database, but remember that you will need the PublicDomain DLL installed in the GAC and you will have to care about the database updates.
UITimeZone and TimeZoneManager
As you can see in the picture above, the required functionality is implemented by two cooperating classes. There is a UITimeZone
class that derives from the abstract System.TimeZone
class. The TimeZone
class contains a property called CurrentTimeZone
which can be used to obtain the current time zone of the machine executing the calling assembly - in case of a Web application, this could be the time zone of the Web server. Similarly, the UITimeZone
class provides the CurrentUITimeZone
property that can be used to get or set the time zone of the user interface. It is an analogy of the CurrentCulture
and CurrentUICulture
properties of the Thread
class.
The TimeZoneManager
class has no special meaning; it's just a static
class providing a list of available time zones and an implementation of the Greenwich Mean Time time zone that could be used as a default value in some scenarios. The list of available time zones is retrieved from the time zone database placed in the Windows registry, so you can rely on Microsoft to update the database when needed. But if you want to be perfect, you should monitor the Windows registry for changes and reload the collection of available time zones every time a modification is made to the time zone database.
If you need more detailed information about time zones and about the way time zones are organized in the Windows registry, see the Mike Dimmick's article or refer to the MSDN Library.
System.TimeZone Issues
There are several reasons why one should not try to use the System.TimeZone
class as a base class. In my Web search for time zone solutions, I've found a thread in the MSDN Forums where a member of the Base Class Libraries development team classifies the attempt to make TimeZone
a point of extensibility as a mistake. First of all, it is unclear what the ToLocalTime()
method should do, whether it should convert from the represented time zone to the machine time zone or from UTC to the represented time zone. I see the latter perfectly suitable for the case of UITimeZone
, because it seems clear to me that TimeZone.CurrentTimeZone.ToLocalTime()
converts to machine local time, and UITimeZone.CurrentUITimeZone.ToLocalTime()
converts to user local time. But I can understand that this could be a matter of opinion.
Another issue does not refer directly to the TimeZone
class, but rather to the internal CurrentSystemTimeZone
class that is used in the implementation of the TimeZone.CurrentTimeZone
property. The problem doesn't appear until you need to work with historical data. Some of the existing time zones use so called Dynamic Daylight Saving Time which means that the dates and times at which the changes to and from Daylight Saving Time occur differ from one year to another. The CurrentSystemTimeZone
class always uses the actual daylight saving time rules, whether the year of the processed time equals to the current year or not. Dynamic daylight changes are stored in the Windows registry, but neither .NET API nor Win32 API (except Windows Vista) provides functionality of querying this data. If you need to work with historical data, which should be the case in database applications and information systems, you may use the UITimeZone
class because it handles historical data correctly.
Using the Code
First of all, you need to implement your own logic of retrieving and updating the user's time zone setting. The sample provided with this article stores the user preferences just in memory, so it is lost when the application ends.
public static TimeZone CurrentUITimeZone
{
get
{
string userName = Thread.CurrentPrincipal.Identity.Name;
return TimeZoneManager.GmtTimeZone;
}
set
{
string userName = Thread.CurrentPrincipal.Identity.Name;
}
}
The following code snippet shows how to use the current user interface time zone. Note that DateTime
values retrieved from a database used to have their Kind
property set to DateTimeKind.Unspecified
, but it may differ with the particular database provider you might be using.
TimeZone currentUITimeZone = UITimeZone.CurrentUITimeZone;
DateTime dbValue = GetDateFromDatabase();
DateTime local = currentUITimeZone.ToLocalTime(dbValue);
local = GetDateFromUser();
dbValue = currentUITimeZone.ToUniversalTime(local);
When you need to modify the user interface time zone, you can use the machine time zone, the Greenwich Mean Time time zone, or you can face the user with a drop-down displaying the list of available time zones which is provided by the TimeZoneManager
class.
UITimeZone.CurrentUITimeZone = TimeZone.CurrentTimeZone;
UITimeZone.CurrentUITimeZone = TimeZoneManager.GmtTimeZone;
UITimeZone.CurrentUITimeZone = TimeZoneManager.TimeZones[33];
UITimeZone.CurrentUITimeZone =
TimeZoneManager.TimeZones["Central Europe Standard Time"];
Conclusion
Hopefully, enumerating the time zones and more will be soon directly supported by the .NET Framework and its TimeZoneInfo
class. Until the .NET Framework 3.5 is released, you might find my classes useful, and I will appreciate any feedback on them. Also feel free to report any bugs discovered in my code. Thanks.
History
- 29th July, 2007: Initial post