Introduction
This article was spurred by discussions on the Channel 9 forums around the time of the daylight changes this year. Some users were complaining that the site was not indicating the posting time accurately. I suggested a route forward but failed to convince the administrators. This article is an attempt to provide code that is useful for converting a time stamp into the end-user's own time zone.
Another group of users also need to deal with the time zones: travelers wishing to know the time at their destination, or the time at home - for example, when trying to call relatives or colleagues. The accompanying application, a demonstration of the TimeZoneInformation
class, is useful here. It could also be useful for working out when a Webcast, for example, occurs in your local time zone.
Time zones
Different countries and locations around the world use different time zones - to match the time shown on a clock with the approximate local time observed. The simplest description is an offset from UTC - Universal Time (Coordinated) - although often erroneously described as an offset from GMT - Greenwich Mean Time. GMT can describe the time zone used in Britain, or a specific offset (UTC+0). The issue is complicated by the Daylight Savings Time; some locations observe daylight savings whereas others do not. Even when they do, they do not agree on the dates and times at which the changes to and from Daylight Savings occur.
Keeping an accurate record of the time zones used around the world is a hard task. Local administrations make rules about the local offset from UTC, and about whether to observe daylight savings, and if so, when. Fortunately, Windows has a database of time zone information installed on every system. Unfortunately, Microsoft failed to provide an API for querying this database.
In addition, Windows provides APIs for discovering the currently selected time zone, for converting from UTC to a specified time zone's local time, and (Windows XP and Server 2003 only) for converting from local time, in a specified zone, to UTC.
.NET APIs
.NET's base class library offers the System.TimeZone
class. This class offers information about the current time zone. However, it does not offer any information about other time zones - what their names are, their offsets, or their daylight savings rules. This class is abstract
, so could be extended. I have not yet done that as some of the features (GetDaylightChanges
, IsDaylightSavingTime
) will be difficult to implement, due to the apparent lack of OS support. A route of investigation would be to inspect the implementation of System.TimeZone
in the Shared Source CLI [^].
The time zone database
Windows NT's time zone database is stored in the registry, at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
. Under this key are the subkeys describing each time zone. Each subkey has the following values (examples taken from the GMT Standard Time key):
Value |
Type |
Purpose |
Example |
Display |
REG_SZ |
Display name |
(GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London |
Dlt |
REG_SZ |
Name for the zone during daylight savings |
GMT Daylight Time |
Index |
REG_DWORD |
Unique index number for zone |
85 |
MapID |
REG_SZ |
Unknown. May be related to Win95 clickable time zone map. |
0,1 |
Std |
REG_SZ |
Name for the zone outside daylight savings |
GMT Standard Time |
TZI |
REG_BINARY |
Offsets and savings start/end date |
see below |
The TZI value is the key part. It contains the offset from UTC, the additional offset for daylight savings, and the start and end dates for daylight savings. The structure is defined as follows:
[StructLayout( LayoutKind.Sequential )]
private struct TZI
{
public int bias;
public int standardBias;
public int daylightBias;
public SYSTEMTIME standardDate;
public SYSTEMTIME daylightDate;
}
The bias
, standardBias
and daylightBias
fields follow the rules for the corresponding members of the Win32 TIME_ZONE_INFORMATION
structure: UTC = local + bias. A negative value indicates that the zone is ahead of UTC (typically east of London) while a positive one indicates behind UTC (typically west of London).
The TimeZoneInformation
class provides access to the time zone database through the EnumZones
static
method. It exposes an Index
property matching the Index
registry value. The value of this field could be stored in a database, to persist, for example, a website user's time zone selection. I do not expect this value to change between operating system versions.
Converting times
The operating system provides two APIs for converting times relative to a time zone: SystemTimeToTzSpecificLocalTime
and TzSpecificLocalTimeToSystemTime
. The former is available only on NT-based operating systems, while the latter is only present on Windows XP and Server 2003 at the time of writing. To use these with a .NET DateTime
structure, we must convert the DateTime
to a SYSTEMTIME
, the TZI
to a TIME_ZONE_INFORMATION
, and convert the resulting SYSTEMTIME
back to a DateTime
. DateTime
conversions to and from SYSTEMTIME
are performed via a FILETIME
using the SystemTimeToFileTime
and FileTimeToSystemTime
APIs.
Mapping a TZI
to a TIME_ZONE_INFORMATION
is simply a matter of assigning the corresponding fields.
The TimeZoneInformation
class converts from UTC to time-zone relative local time using the FromUniversalTime
method, and from a time-zone relative local time to UTC using the ToUniversalTime
method. This latter method will only work on Windows XP or Windows Server 2003; on down-level operating systems (Windows NT 4.0, Windows 2000) it throws a NotSupportedException
.
The sample application
The supplied sample application converts either the current time, or a user-selected date and time, from the user's current time zone (or a selected time zone) to a user-selected time zone. The time zone selection defaults to the user's current time zone, so initially, the destination time will be the same as the current zone.
If the user selects Use Current, the local and the destination time update with the system clock.
If the Time Zone checkbox under Local Time is selected, the corresponding drop-down list can be used to select the time zone to convert from. On Windows 2000 or earlier, if this checkbox is selected, an error message will be displayed and the checkbox is subsequently disabled, reflecting the fact that the feature cannot be supported. The selected date and time are converted from the source time zone to UTC, and then from UTC to the destination zone.
I admit that the user interface is basic! I'm no graphic designer. Nevertheless, I hope it will be useful.
Updates
- Version 1.2
TimeZoneInformation
class: A bug-fix was made to the CurrentTimeZone
static
property to cope with the 'Automatically adjust clock for daylight saving changes' checkbox in the Date and Time control panel applet being unchecked. If this checkbox is unchecked, the GetTimeZoneInformation
call returns a structure where DaylightBias
and DaylightName
are equal to their standard equivalents. This caused the comparison to fail.
- World clock application: A bug-fix to prevent a crash if no time zone was selected in the Source Zone drop-down when the checkbox was checked.
- Version 1.1
TimeZoneInformation
class: The ToUniversalTime
method was added to perform conversions to UTC using the TzSpecificLocalTimeToSystemTime
API. Other new methods: FromIndex
, a static
method to locate a TimeZoneInformation
object for a recorded Index
, and static overloads of FromUniversalTime
and ToUniversalTime
which take an index
argument.
- World clock application: The Local Time Zone checkbox and drop-down list, allowing the user to specify the zone to convert from.