Sorting TimeZoneInfo.GetSystemTimeZones() properly

The documentation for System.TimeZoneInfo.GetSystemTimeZones() states:

Returns a sorted collection of all the time zones about which information is available on the local system.

Unfortunately, while the result is technically sorted, it’s a silly sort which shows GMT, then the eastern hemisphere, then the western hemisphere. It seems to be sorted by TimeZoneInfo.DisplayName instead of TimeZoneInfo.BaseUtcOffset.

Luckily this is easily fixed (and concise too thanks to anonymous methods!).

List<TimeZoneInfo> timeZones = new List<TimeZoneInfo>(TimeZoneInfo.GetSystemTimeZones());
timeZones.Sort(delegate(TimeZoneInfo left, TimeZoneInfo right) {
    int comparison = left.BaseUtcOffset.CompareTo(right.BaseUtcOffset);
    return comparison == 0 ? string.CompareOrdinal(left.DisplayName, right.DisplayName) : comparison;
});

In the devil’s language (VB.NET) you can achieve it using some slightly different code (no anonymous methods in VB.NET):

Dim timeZones As List(Of TimeZoneInfo) = New List(Of TimeZoneInfo)(TimeZoneInfo.GetSystemTimeZones())
timeZones.Sort(New Comparison(Of TimeZoneInfo)(AddressOf CompareTimeZones))

Private Function CompareTimeZones(ByVal left As TimeZoneInfo, ByVal right As TimeZoneInfo) As Integer
    Return IIf(comparison = 0, String.CompareOrdinal(left.DisplayName, right.DisplayName), comparison)
End Function

This will result in the list being sorted in the same order that you see under “Adjust Date/Time” in Windows.

Maybe they could fix this in the next release. 🙂 To help make that happen, vote for the issue on Microsoft Connect.

Update – 5 Feb 08: Daniella asked for a VB.NET version, so I’ve updated the post with one. Because VB.NET doesn’t support anonymous methods, you need an extra function somewhere to do the comparison.

Update – 12 Feb 08: Whitney correctly pointed out that the order still wasn’t quite right for time zones that shared the same offset. We need to sort by name as well. I’ve updated the C# and VB.NET version accordingly. I changed Whitney’s version a bit to use the conditional operator so that I could avoid two return statements, as well as translating it to VB.NET. Thanks Whitney!

Update – 15 Feb 08: Added Microsoft Connect link.

9 comments

  1. I have the same problem but I work in VB and I have no matter to trasnlate it to VB… May somebody help me?!

  2. Great find, thanks! However, the time zone order that I get is not exactly the same as that in the canned Windows time zone dialog box. By using just the base UTC offset for comparison, I get time zones with the same base UTC offset that aren’t always sorted alphabetically, like they are in the Windows dialog. For instance, the time zones I get back with a UTC offset of GMT-06:00 are, in this order:

    (GMT-06:00) Central Time (US & Canada)
    (GMT-06:00) Central America
    (GMT-06:00) Saskatchewan
    (GMT-06:00) Guadalajara, Mexico City, Monterrey

    In the Windows time zone pull-down box, the order is alphabetical:

    (GMT-06:00) Central America
    (GMT-06:00) Central Time (US & Canada)
    (GMT-06:00) Guadalajara, Mexico City, Monterrey
    (GMT-06:00) Saskatchewan

    So, I added a secondary comparison based on TimeZoneInfo.DisplayName, using String.CompareOrdinal() (from using Reflector to see how .NET does its display name comparison):

    List timeZones = new List(TimeZoneInfo.GetSystemTimeZones());
    timeZones.Sort(delegate(TimeZoneInfo left, TimeZoneInfo right)
    {
    int comparison = left.BaseUtcOffset.CompareTo(right.BaseUtcOffset);
    if (comparison == 0)
    {
    return string.CompareOrdinal(left.DisplayName, right.DisplayName);
    }
    return comparison;
    });

    This will give you alphabetical sorts for time zones with the same base UTC offset, and your results should match the built-in Windows time zone list exactly. 🙂

    Whitney Kew

  3. Unfortunately, it looks like the angle brackets were removed in my last post; of course, List should be List (left angle bracket)TimeZoneInfo(right angle bracket).

    🙂

  4. Another way to do this is to use Linq:

    TimeZoneInfo.GetSystemTimeZones().OrderBy(Function(zone As TimeZoneInfo) zone.BaseUtcOffset.TotalHours)

  5. Very useful post! There is a line missing from the VB code. Here is the full working code:

    Public Function GetSensibleSortedTimeZones() As List(Of TimeZoneInfo)

    Dim timeZones As List(Of TimeZoneInfo) = New List(Of TimeZoneInfo)(TimeZoneInfo.GetSystemTimeZones())
    timeZones.Sort(New Comparison(Of TimeZoneInfo)(AddressOf CompareTimeZones))

    Return timeZones

    End Function

    Private Function CompareTimeZones(ByVal left As TimeZoneInfo, ByVal right As TimeZoneInfo) As Integer

    Dim comparison As Integer = left.BaseUtcOffset.CompareTo(right.BaseUtcOffset)
    ‘ If BaseUtcOffset identical then sort by DisplayName as well
    Return IIf(comparison = 0, String.CompareOrdinal(left.DisplayName, right.DisplayName), comparison)

    End Function

Comments are closed.