69 votes

.NET TimeZoneInfo à partir du fuseau horaire d'Olson

Comment puis-je convertir ce qui suit en System.TimeZone ou System.TimeZoneInfo ?

{
  "timeZone": "America/Los_Angeles", 
  "currentOffsetMs": -25200000
}

Il s'agit de données que je reçois en retour d'un service web tiers.

Je suppose que le décalage est la différence par rapport à UTC, et on me dit que "America/Los_Angeles" est un fuseau horaire Olson. Java n'a aucun problème à analyser cette information dans un TimeZone Java, mais j'ai besoin de l'analyser dans un objet TimeZoneInfo C#.

1 votes

Il n'existe pas de fuseau horaire portant ce nom. Le décalage obtenu n'est pas très utile non plus, il indique seulement que le décalage est de -7 heures par rapport à UTC. Ce qui n'est pas très utile pour trouver le fuseau horaire Pacifique dans lequel se trouve Los Angeles, qui a un décalage de -8 heures par rapport à UTC. Vous obtenez -7 parce que l'heure d'été est en vigueur. Cela va changer. S'il le faut, vous pouvez utiliser cette liste pour traduire : fr.wikipedia.org/wiki/List_of_tz_database_time_zones

1 votes

Cette liste de base de données TZ est également disponible sous forme de bibliothèque .NET : codeplex.com/zoneinfo Cependant, cela n'aide pas non plus ; elle ne renvoie rien qui puisse être mis en correspondance avec un TimeZoneInfo .NET standard. Grrrr.

5 votes

@HansPassant - America/Los_Angeles est un identifiant de fuseau horaire provenant de la base de données de l'IANA sur les fuseaux horaires. fr.wikipedia.org/wiki/America/Los_Angeles .

107voto

Judah Himango Points 27365

Cette page d'Unicode.org contient une table "Olson time zone to Win32 time zone". À partir de là, j'ai créé une jolie petite fonction d'aide en C# pour mapper la chaîne du fuseau horaire Olson en un TimeZoneInfo .NET :

/// <summary>
/// Converts an Olson time zone ID to a Windows time zone ID.
/// </summary>
/// <param name="olsonTimeZoneId">An Olson time zone ID. See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html. </param>
/// <returns>
/// The TimeZoneInfo corresponding to the Olson time zone ID, 
/// or null if you passed in an invalid Olson time zone ID.
/// </returns>
/// <remarks>
/// See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
/// </remarks>
public static TimeZoneInfo OlsonTimeZoneToTimeZoneInfo(string olsonTimeZoneId)
{
    var olsonWindowsTimes = new Dictionary<string, string>()
    {
        { "Africa/Bangui", "W. Central Africa Standard Time" },
        { "Africa/Cairo", "Egypt Standard Time" },
        { "Africa/Casablanca", "Morocco Standard Time" },
        { "Africa/Harare", "South Africa Standard Time" },
        { "Africa/Johannesburg", "South Africa Standard Time" },
        { "Africa/Lagos", "W. Central Africa Standard Time" },
        { "Africa/Monrovia", "Greenwich Standard Time" },
        { "Africa/Nairobi", "E. Africa Standard Time" },
        { "Africa/Windhoek", "Namibia Standard Time" },
        { "America/Anchorage", "Alaskan Standard Time" },
        { "America/Argentina/San_Juan", "Argentina Standard Time" },
        { "America/Asuncion", "Paraguay Standard Time" },
        { "America/Bahia", "Bahia Standard Time" },
        { "America/Bogota", "SA Pacific Standard Time" },
        { "America/Buenos_Aires", "Argentina Standard Time" },
        { "America/Caracas", "Venezuela Standard Time" },
        { "America/Cayenne", "SA Eastern Standard Time" },
        { "America/Chicago", "Central Standard Time" },
        { "America/Chihuahua", "Mountain Standard Time (Mexico)" },
        { "America/Cuiaba", "Central Brazilian Standard Time" },
        { "America/Denver", "Mountain Standard Time" },
        { "America/Fortaleza", "SA Eastern Standard Time" },
        { "America/Godthab", "Greenland Standard Time" },
        { "America/Guatemala", "Central America Standard Time" },
        { "America/Halifax", "Atlantic Standard Time" },
        { "America/Indianapolis", "US Eastern Standard Time" },
        { "America/Indiana/Indianapolis", "US Eastern Standard Time" },
        { "America/La_Paz", "SA Western Standard Time" },
        { "America/Los_Angeles", "Pacific Standard Time" },
        { "America/Mexico_City", "Mexico Standard Time" },
        { "America/Montevideo", "Montevideo Standard Time" },
        { "America/New_York", "Eastern Standard Time" },
        { "America/Noronha", "UTC-02" },
        { "America/Phoenix", "US Mountain Standard Time" },
        { "America/Regina", "Canada Central Standard Time" },
        { "America/Santa_Isabel", "Pacific Standard Time (Mexico)" },
        { "America/Santiago", "Pacific SA Standard Time" },
        { "America/Sao_Paulo", "E. South America Standard Time" },
        { "America/St_Johns", "Newfoundland Standard Time" },
        { "America/Tijuana", "Pacific Standard Time" },
        { "Antarctica/McMurdo", "New Zealand Standard Time" },
        { "Atlantic/South_Georgia", "UTC-02" },
        { "Asia/Almaty", "Central Asia Standard Time" },
        { "Asia/Amman", "Jordan Standard Time" },
        { "Asia/Baghdad", "Arabic Standard Time" },
        { "Asia/Baku", "Azerbaijan Standard Time" },
        { "Asia/Bangkok", "SE Asia Standard Time" },
        { "Asia/Beirut", "Middle East Standard Time" },
        { "Asia/Calcutta", "India Standard Time" },
        { "Asia/Colombo", "Sri Lanka Standard Time" },
        { "Asia/Damascus", "Syria Standard Time" },
        { "Asia/Dhaka", "Bangladesh Standard Time" },
        { "Asia/Dubai", "Arabian Standard Time" },
        { "Asia/Irkutsk", "North Asia East Standard Time" },
        { "Asia/Jerusalem", "Israel Standard Time" },
        { "Asia/Kabul", "Afghanistan Standard Time" },
        { "Asia/Kamchatka", "Kamchatka Standard Time" },
        { "Asia/Karachi", "Pakistan Standard Time" },
        { "Asia/Katmandu", "Nepal Standard Time" },
        { "Asia/Kolkata", "India Standard Time" },
        { "Asia/Krasnoyarsk", "North Asia Standard Time" },
        { "Asia/Kuala_Lumpur", "Singapore Standard Time" },
        { "Asia/Kuwait", "Arab Standard Time" },
        { "Asia/Magadan", "Magadan Standard Time" },
        { "Asia/Muscat", "Arabian Standard Time" },
        { "Asia/Novosibirsk", "N. Central Asia Standard Time" },
        { "Asia/Oral", "West Asia Standard Time" },
        { "Asia/Rangoon", "Myanmar Standard Time" },
        { "Asia/Riyadh", "Arab Standard Time" },
        { "Asia/Seoul", "Korea Standard Time" },
        { "Asia/Shanghai", "China Standard Time" },
        { "Asia/Singapore", "Singapore Standard Time" },
        { "Asia/Taipei", "Taipei Standard Time" },
        { "Asia/Tashkent", "West Asia Standard Time" },
        { "Asia/Tbilisi", "Georgian Standard Time" },
        { "Asia/Tehran", "Iran Standard Time" },
        { "Asia/Tokyo", "Tokyo Standard Time" },
        { "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time" },
        { "Asia/Vladivostok", "Vladivostok Standard Time" },
        { "Asia/Yakutsk", "Yakutsk Standard Time" },
        { "Asia/Yekaterinburg", "Ekaterinburg Standard Time" },
        { "Asia/Yerevan", "Armenian Standard Time" },
        { "Atlantic/Azores", "Azores Standard Time" },
        { "Atlantic/Cape_Verde", "Cape Verde Standard Time" },
        { "Atlantic/Reykjavik", "Greenwich Standard Time" },
        { "Australia/Adelaide", "Cen. Australia Standard Time" },
        { "Australia/Brisbane", "E. Australia Standard Time" },
        { "Australia/Darwin", "AUS Central Standard Time" },
        { "Australia/Hobart", "Tasmania Standard Time" },
        { "Australia/Perth", "W. Australia Standard Time" },
        { "Australia/Sydney", "AUS Eastern Standard Time" },
        { "Etc/GMT", "UTC" },
        { "Etc/GMT+11", "UTC-11" },
        { "Etc/GMT+12", "Dateline Standard Time" },
        { "Etc/GMT+2", "UTC-02" },
        { "Etc/GMT-12", "UTC+12" },
        { "Europe/Amsterdam", "W. Europe Standard Time" },
        { "Europe/Athens", "GTB Standard Time" },
        { "Europe/Belgrade", "Central Europe Standard Time" },
        { "Europe/Berlin", "W. Europe Standard Time" },
        { "Europe/Brussels", "Romance Standard Time" },
        { "Europe/Budapest", "Central Europe Standard Time" },
        { "Europe/Dublin", "GMT Standard Time" },
        { "Europe/Helsinki", "FLE Standard Time" },
        { "Europe/Istanbul", "GTB Standard Time" },
        { "Europe/Kiev", "FLE Standard Time" },
        { "Europe/London", "GMT Standard Time" },
        { "Europe/Minsk", "E. Europe Standard Time" },
        { "Europe/Moscow", "Russian Standard Time" },
        { "Europe/Paris", "Romance Standard Time" },
        { "Europe/Sarajevo", "Central European Standard Time" },
        { "Europe/Warsaw", "Central European Standard Time" },
        { "Indian/Mauritius", "Mauritius Standard Time" },
        { "Pacific/Apia", "Samoa Standard Time" },
        { "Pacific/Auckland", "New Zealand Standard Time" },
        { "Pacific/Fiji", "Fiji Standard Time" },
        { "Pacific/Guadalcanal", "Central Pacific Standard Time" },
        { "Pacific/Guam", "West Pacific Standard Time" },
        { "Pacific/Honolulu", "Hawaiian Standard Time" },
        { "Pacific/Pago_Pago", "UTC-11" },
        { "Pacific/Port_Moresby", "West Pacific Standard Time" },
        { "Pacific/Tongatapu", "Tonga Standard Time" }
    };

    var windowsTimeZoneId = default(string);
    var windowsTimeZone = default(TimeZoneInfo);
    if (olsonWindowsTimes.TryGetValue(olsonTimeZoneId, out windowsTimeZoneId))
    {
        try { windowsTimeZone = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZoneId); }
        catch (TimeZoneNotFoundException) { }
        catch (InvalidTimeZoneException) { }
    }
    return windowsTimeZone;
}

0 votes

Malheureusement, ce tableau est incomplet. Unicode.org fournit une correspondance Windows TZ -> Olson, et non l'inverse. Olson possède également de nombreux méta-noms, tels que Europe/Zurich, qui ne sont pas couverts par la correspondance inverse.

0 votes

Bon à savoir. Connaissez-vous une liste complète des noms d'Olson ? Je mettrais volontiers à jour cet article.

0 votes

Une liste plus complète est disponible à l'adresse suivante stackoverflow.com/questions/8372537/ ... Je suis actuellement en train de vérifier qu'il est complet et exact.

30voto

Matt Johnson Points 33433

Voici une fonction de mappage inverse (tzdb -> Windows) utilisant la fonction NodaTime :

using NodaTime;
using NodaTime.TimeZones;

...

public TimeZoneInfo GetTimeZoneInfoForTzdbId(string tzdbId)
{
  var mappings = TzdbDateTimeZoneSource.Default.WindowsMapping.MapZones;
  var map = mappings.FirstOrDefault(x =>
      x.TzdbIds.Any(z => z.Equals(tzdbId, StringComparison.OrdinalIgnoreCase)));
  return map == null ? null : TimeZoneInfo.FindSystemTimeZoneById(map.WindowsId);
}

Notez qu'il est possible qu'il y ait plus d'une correspondance (dans ce cas, la première correspondance trouvée est utilisée), ou qu'il n'y ait pas de correspondance du tout (dans ce cas, la valeur retournée est nulle).

Dans les fuseaux horaires les plus courants, cela devrait suffire. Mais la meilleure solution serait de ne pas utiliser TimeZoneInfo et utiliser NodaTime dans l'ensemble de l'application, directement avec la zone TZDB dont vous disposez.

Voir aussi Comment traduire les fuseaux horaires de Windows et de l'IANA ?

12voto

Stephen Kennedy Points 1458

Vous voudrez peut-être consulter l'ouvrage de Jon Skeet intitulé Noda-Time et abandonner complètement TimeZoneInfo. Noda-Time utilise les fuseaux horaires d'Olson, votre correspondance sera donc un jeu d'enfant. Il y a d'autres raisons pour lesquelles vous pourriez vouloir l'utiliser :

Quel est le problème avec DateTime ?

Question de SO dont le résultat a été l'utilisation de Noda-Time

11voto

Hosney Points 667

J'ai créé un petit extrait pour obtenir une liste de correspondances entre les fuseaux horaires d'Olson et de Windows à partir du fichier xml qui se trouve à l'adresse suivante http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml

private static void LoadMappingsO()
{
    var file = new FileInfo("windowsZones.xml");
    if (!file.Exists)
    {
        return;
    }

    var map = new Dictionary<string, string>();
    using (var reader = file.OpenText())
    {
        var readerSettings = new XmlReaderSettings { XmlResolver = null, ProhibitDtd = false };

        using (var xmlReader = XmlReader.Create(reader, readerSettings))
        {
            var document = new XPathDocument(xmlReader);
            var navigator = document.CreateNavigator();

            var nodes = navigator.Select("/supplementalData/windowsZones/mapTimezones/mapZone");

            while (nodes.MoveNext())
            {
                var node = nodes.Current;
                if (node == null) continue;

                var olsonNames = node.GetAttribute("type", "").Split(' ');
                var windowsName = node.GetAttribute("other", "");
                foreach (var olsonName in olsonNames)
                {
                    if (!map.ContainsKey(olsonName))
                    {
                        map.Add(olsonName, windowsName);
                    }
                }
            }
        }
    }

    using (TextWriter tw = new StreamWriter("dict.txt", false))
    {
        foreach (var key in map.Keys)
        {
            tw.WriteLine(string.Format("{{\"{0}\", \"{1}\"}},", key, map[key]));
        }
    }
}

UPDATE (Utilisation de Linq Xml) :

private static void LoadMappings()
{
    var map = new Dictionary<string, string>();
    var xdoc = XDocument.Load("windowsZones.xml");

    var zones = xdoc.XPathSelectElements("/supplementalData/windowsZones/mapTimezones/mapZone");
    foreach (var zone in zones)
    {
        var olsonNames = zone.Attribute("type")?.Value.Split(' ');
        if (olsonNames == null)
            continue;

        var windowsName = zone.Attribute("other")?.Value;
        if (string.IsNullOrWhiteSpace(windowsName))
            continue;

        foreach (var olsonName in olsonNames)
        {
            map[olsonName] = windowsName;
        }
    }

    using (TextWriter tw = new StreamWriter("dict.txt", false))
    {
        foreach (var key in map.Keys)
        {
            tw.WriteLine($"{{\"{key}\", \"{map[key]}\"}},");
        }
    }
}

0 votes

C'est une bonne chose. Je me demande si cela ne pourrait pas être plus propre via LINQ-to-XML. Mais merci.

2 votes

@Hosney oui mais s'il vous plaît n'écrivez pas de code qui récupère automatiquement les fichiers de unicode.org. Téléchargez une version de CLDR et incluez le fichier dans votre application. Cette URL fait l'objet de téléchargements abusifs en ce moment.

0 votes

Le code ne récupère PAS les fichiers de unicode.org. Comme vous pouvez le voir, le code utilise FileInfo qui lit le fichier xml stocké localement dans le chemin de l'application.

2voto

Ed Bayiates Points 6517

Après avoir converti le currentOffsetMs en heures et en minutes restantes, vous pouvez énumérer les objets TimeZoneInfo définis :

foreach (TimeZoneInfo nextZone in TimeZoneInfo.GetSystemTimeZones())
{
    int nextHours = nextZone.BaseUtcOffset.Hours + 24;     // To prevent negative numbers
    int nextMinutes = nextZone.BaseUtcOffset.Minutes;
    if (tzHours == nextHours && tzMinutes == nextMinutes)
    {
        myTimeZoneInfo = nextZone;
        break;
    }
}

1 votes

Utile, mais pas assez précis. Plusieurs fuseaux horaires ont le même décalage "UTC-07:00 Chihuahua, La Paz, Mazatlan" et "UTC-07:00 Arizona" partagent le même décalage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X