سؤال

I'm using NodaTime because of its nice support for zoneinfo data, however I have a case where I need to convert the DateTimeZone into TimeZoneInfo for use in Quartz.NET.

What's the recommended approach here? IANA has a mapping file between Windows time zones and zoneinfo time zones, can I create an extension method that utilises this information?

هل كانت مفيدة؟

المحلول

I would avoid using reflection if possible. I wouldn't like to bet on your approach working with future versions :)

Feel free to file a feature request for this functionality for future versions, but for the moment I'd build up your reverse dictionary in a more stable way:

// Note: this version lets you work with any IDateTimeZoneSource, although as the only
// other built-in source is BclDateTimeZoneSource, that may be less useful :)
private static IDictionary<string, string> LoadTimeZoneMap(IDateTimeZoneSource source)
{
    var nodaToWindowsMap = new Dictionary<string, string>();
    foreach (var bclZone in TimeZoneInfo.GetSystemTimeZones())
    {
        var nodaId = source.MapTimeZoneId(bclZone);
        if (nodaId != null)
        {
            nodaToWindowsMap[nodaId] = bclZone.Id;
        }
    }
    return nodaToWindowsMap;
}

Of course, this won't cover all the time zones in TZDB. In fact, it won't even give all the information we could give based on the CLDR information we use... CLDR gives multiple mappings for each Windows ID, and we only store the first one at the moment. We've been trying to work out how to expose more of that, but haven't managed yet. Thoughts welcome on the Noda Time mailing list :)

Also note that just because there's a mapping between the BCL and TZDB zones doesn't mean they'll actually give the same results for everything - it's just the closest mapping available.

نصائح أخرى

Aha, I found it - TzdbDateTimeZoneSource has a MapTimeZoneId method that I can pop into TimeZoneInfo.FindSystemTimeZoneById.

Edit: MapTimeZoneId does the mapping from Windows time zone into zoneinfo... I ended up resorting to reflection to do the mapping in the opposite direction:

using System;
using System.Collections.Generic;
using System.Reflection;

using NodaTime;
using NodaTime.TimeZones;

/// <summary>
/// Extension methods for <see cref="DateTimeZone" />.
/// </summary>
internal static class DateTimeZoneExtensions
{
    private static readonly Lazy<IDictionary<string, string>> map = new Lazy<IDictionary<string, string>>(LoadTimeZoneMap, true);

    public static TimeZoneInfo ToTimeZoneInfo(this DateTimeZone timeZone)
    {
        string id;
        if (!map.Value.TryGetValue(timeZone.Id, out id))
        {
            throw new TimeZoneNotFoundException(string.Format("Could not locate time zone with identifier {0}", timeZone.Id));
        }

        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(id);
        if (timeZoneInfo == null)
        {
            throw new TimeZoneNotFoundException(string.Format("Could not locate time zone with identifier {0}", timeZone.Id));
        }

        return timeZoneInfo;
    }

    private static IDictionary<string, string> LoadTimeZoneMap()
    {
        TzdbDateTimeZoneSource source = new TzdbDateTimeZoneSource("NodaTime.TimeZones.Tzdb");
        FieldInfo field = source.GetType().GetField("windowsIdMap", BindingFlags.Instance | BindingFlags.NonPublic);
        IDictionary<string, string> map = (IDictionary<string, string>)field.GetValue(source);

        // reverse the mappings
        Dictionary<string, string> reverseMap = new Dictionary<string, string>();
        foreach (KeyValuePair<string, string> kvp in map)
        {
            reverseMap.Add(kvp.Value, kvp.Key);
        }

        return reverseMap;
    }
}

You can use TimeZoneConverter library by Matt Johnson.

ZoneId used by NodeTime TzdbZoneLocation is IANA time zone, so you can get TimeZoneInfo like this:

string windowsTimeZoneName = TZConvert.IanaToWindows(tzdbZoneLocation.ZoneId);
var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZoneName);

Don't forget to wrap it with try-catch with some kind of fallback just in case.

Also look at original Matt Johnson solution for converting between IANA time zone and Windows time zone.

However none of this works in a PCL because most of the work is done by .NET in the .GetSystemTImeZones() and .FindSystemTIemZoneById() methods - which don't exist in PCL.

I'm stunned that for all the info you can get out of NodaTime, getting something as simple as "EST" abbreviation when you already have the zone name of "US/Eastern" seems to have stopped me in my tracks.

With NodaTime assembly version 3.0.9.0 the mapping of tzdb time zone ids (NodaTime.DateTimeZone.Id) to dotnet time zone info ids (System.TimeZoneInfo.Id) and the reverse process is achievable via these two string key and value dictionaries: NodaTime.TimeZones.TzdbDateTimeZoneSource.Default.TzdbToWindowsIds, NodaTime.TimeZones.TzdbDateTimeZoneSource.Default.WindowsToTzdbIds.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top