Question

I am using this piece of code to convert "Eastern Time Zone" to "EST". Now it is showing "EDT". You dont see that abbr that often in places and would like to stick to "EST". How do I do this with NodaTime?

 public static string GetTimeZoneAbbr(string timeZone)
        {

            var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);

            if (timeZoneInfo != null)
            {
                var dateTime = DateTime.UtcNow;
                var instant = Instant.FromDateTimeUtc(dateTime);
                var tzdbSource = TzdbDateTimeZoneSource.Default;
                var tzid = tzdbSource.MapTimeZoneId(timeZoneInfo);
                var dateTimeZone = DateTimeZoneProviders.Tzdb[tzid];
                var zoneInterval = dateTimeZone.GetZoneInterval(instant);
                return zoneInterval.Name;
            }

            return string.Empty;
        }
Was it helpful?

Solution

UPDATE

The answer below described how to parse and use the CLDR data. This is fine, but I've made it much easier by encompassing this all in a library. See this StackOverflow answer, read my blog post, and take a look at the TimeZoneNames library. Using this library is much easier than parsing the CLDR data yourself.

// You can pass either type of time zone identifier:
var tz = "America/New_York";       // IANA
var tz = "Eastern Standard Time";  // Windows

// You can get names or abbreviations for any language or locale
var names = TZNames.GetNamesForTimeZone(tz, "en-US");
var abbreviations = TZNames.GetAbbreviationsForTimeZone(tz, "en-US");

names.Generic == "Eastern Time"
names.Standard == "Eastern Standard Time"
names.Daylight == "Eastern Daylight Time"

abbreviations.Generic == "ET"
abbreviations.Standard == "EST"
abbreviations.Daylight == "EDT"

ORIGINAL ANSWER

I've written a bit in the question comments about why it's perfectly valid to show the abbreviated form, but allow me to also answer the question as it was asked.

Reiterating your question another way, you wish to start with a Microsoft Windows time zone id, and end up with a human-readable string that represents the entire time zone, and not just the time zone segment that is in effect.

You could just give them the TimeZoneInfo.DisplayName, but that isn't always going to be appropriate. For the US, you might get a display name back of "(UTC-05:00) Eastern Time (US & Canada), and you could strip off the leading offset and parenthesis to just give back "Eastern Time (US & Canada)". But that's not going to work for all time zones, since many just have display names that list cities, such as "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan".

A better approach would be to use the data from the Unicode CLDR Project. Noda Time has a portion of this data, but not everything you need for this particular problem. So I can't give you a code example that uses Noda Time. However, you can use the following steps against the raw CLDR data to achieve your goal:

  1. Find the IANA time zone ID corresponding to the Windows time zone, such as you've already done in the code above, or using the CLDR Windows time zone mappings directly.

  2. Lookup the IANA time zone in the CLDR MetaZones file.

  3. Lookup the MetaZone in one of the CLDR translation data files, or charts such as this one. Use the "generic-long" or "generic-short" pattern, and the language of your choice, such as "en" for English.

So, in your case, starting with the Windows TimeZoneInfo.Id of "Eastern Standard Time":

  1. IANA Zone = "America/New_York"

  2. CLDR MetaZone = "America_Eastern"

  3. generic-long [en] = "Eastern Time"

    generic-short [en] = "ET"

Note that not every Windows time zone is mappable to an IANA zone, not every meta zone has a short name, and some zones that have never followed daylight saving time will only have a standard name instead of a generic name.

Here is some C# code that shows how to traverse the CLDR's XML data to get the generic long names for the TimeZoneInfo objects. It assumes you have access to the CLDR data at the path specified. Download the latest core.zip and extract, then point the basePath at that folder.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

static Dictionary<TimeZoneInfo, string> GetCldrGenericLongNames(string basePath, string language)
{
    // Set some file paths
    string winZonePath = basePath + @"\common\supplemental\windowsZones.xml";
    string metaZonePath = basePath + @"\common\supplemental\metaZones.xml";
    string langDataPath = basePath + @"\common\main\" + language + ".xml";

    // Make sure the files exist
    if (!File.Exists(winZonePath) || !File.Exists(metaZonePath) || !File.Exists(langDataPath))
    {
        throw new FileNotFoundException("Could not find CLDR files with language '" + language + "'.");
    }

    // Load the data files
    var xmlWinZones = XDocument.Load(winZonePath);
    var xmlMetaZones = XDocument.Load(metaZonePath);
    var xmlLangData = XDocument.Load(langDataPath);

    // Prepare the results dictionary
    var results = new Dictionary<TimeZoneInfo, string>();

    // Loop for each Windows time zone
    foreach (var timeZoneInfo in TimeZoneInfo.GetSystemTimeZones())
    {
        // Get the IANA zone from the Windows zone
        string pathToMapZone = "/supplementalData/windowsZones/mapTimezones/mapZone" +
                               "[@territory='001' and @other='" + timeZoneInfo.Id + "']";
        var mapZoneNode = xmlWinZones.XPathSelectElement(pathToMapZone);
        if (mapZoneNode == null) continue;
        string primaryIanaZone = mapZoneNode.Attribute("type").Value;

        // Get the MetaZone from the IANA zone
        string pathToMetaZone = "/supplementalData/metaZones/metazoneInfo/timezone[@type='" + primaryIanaZone +  "']/usesMetazone";
        var metaZoneNode = xmlMetaZones.XPathSelectElements(pathToMetaZone).LastOrDefault();
        if (metaZoneNode == null) continue;
        string metaZone = metaZoneNode.Attribute("mzone").Value;

        // Get the generic name for the MetaZone
        string pathToNames = "/ldml/dates/timeZoneNames/metazone[@type='" + metaZone + "']/long";
        var nameNodes = xmlLangData.XPathSelectElement(pathToNames);
        var genericNameNode = nameNodes.Element("generic");
        var standardNameNode = nameNodes.Element("standard");
        string name = genericNameNode != null
            ? genericNameNode.Value
            : standardNameNode != null
                ? standardNameNode.Value
                : null;

        // If we have valid results, add to the dictionary
        if (name != null)
        {
            results.Add(timeZoneInfo, name);
        }
    }

    return results;
}

Calling this will get you a dictionary which you can then use for lookups. Example:

// load the data once an cache it in a static variable
const string basePath = @"C:\path\to\extracted\cldr\core";
private static readonly Dictionary<TimeZoneInfo, string> timeZoneNames = 
    GetCldrGenericLongNames(basePath, "en");

// then later access it like this
string tzname = timeZoneNames[yourTimeZoneInfoObject];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top