Partager via


Mondialisation

La mondialisation implique la conception et le développement d’une application prête pour le monde qui prend en charge les interfaces localisées et les données régionales pour les utilisateurs de plusieurs cultures. Avant de commencer la phase de conception, vous devez déterminer quelles cultures votre application prendra en charge. Bien qu’une application cible une seule culture ou région par défaut, vous pouvez la concevoir et l’écrire afin qu’elle puisse facilement être étendue aux utilisateurs d’autres cultures ou régions.

En tant que développeurs, nous avons toutes des hypothèses sur les interfaces utilisateur et les données formées par nos cultures. Par exemple, pour un développeur anglophone aux États-Unis, la sérialisation des données de date et d’heure sous forme de chaîne au format MM/dd/yyyy hh:mm:ss semble parfaitement raisonnable. Toutefois, la désérialisation de cette chaîne sur un système dans un contexte culturel différent est susceptible de provoquer une exception ou de produire des FormatException données inexactes. La mondialisation nous permet d’identifier ces hypothèses propres à la culture et de s’assurer qu’elles n’affectent pas la conception ou le code de notre application.

Cet article traite de certains des principaux problèmes que vous devez prendre en compte et les meilleures pratiques que vous pouvez suivre lors de la gestion des chaînes, des valeurs de date et d’heure et des valeurs numériques dans une application globalisée.

Chaînes

La gestion des caractères et des chaînes est un objectif central de la mondialisation, car chaque culture ou région peut utiliser des caractères et des jeux de caractères différents et les trier différemment. Cette section fournit des recommandations pour l’utilisation de chaînes dans des applications globalisées.

Utiliser Unicode en interne

Par défaut, .NET utilise des chaînes Unicode. Une chaîne Unicode se compose de zéro, un ou plusieurs Char objets, chacun représentant une unité de code UTF-16. Il existe une représentation Unicode pour presque tous les caractères de chaque jeu de caractères en cours d’utilisation dans le monde entier.

De nombreuses applications et systèmes d’exploitation, y compris le système d’exploitation Windows, peuvent également utiliser des pages de codes pour représenter des jeux de caractères. Les pages de code contiennent généralement les valeurs ASCII standard de 0x00 via 0x7F et mappent d’autres caractères aux valeurs restantes de 0x80 via 0xFF. L’interprétation des valeurs de 0x80 par 0xFF dépend de la page de codes spécifique. En raison de cela, vous devez éviter d’utiliser des pages de code dans une application globalisée si possible.

L’exemple suivant illustre les dangers de l’interprétation des données de page de codes lorsque la page de codes par défaut d’un système est différente de la page de codes sur laquelle les données ont été enregistrées. (Pour simuler ce scénario, l’exemple spécifie explicitement différentes pages de code.) Tout d’abord, l’exemple définit un tableau qui se compose des caractères majuscules de l’alphabet grec. Il les encode dans un tableau d’octets à l’aide de la page de codes 737 (également appelée MS-DOS grec) et enregistre le tableau d’octets dans un fichier. Si le fichier est récupéré et que son tableau d’octets est décodé à l’aide de la page de codes 737, les caractères d’origine sont restaurés. Toutefois, si le fichier est récupéré et que son tableau d’octets est décodé à l’aide de la page de codes 1252 (ou Windows-1252, qui représente des caractères dans l’alphabet latin), les caractères d’origine sont perdus.

using System;
using System.IO;
using System.Text;

public class Example
{
    public static void CodePages()
    {
        // Represent Greek uppercase characters in code page 737.
        char[] greekChars =
        {
            'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ',
            'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π',
            'Ρ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω'
        };

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        Encoding cp737 = Encoding.GetEncoding(737);
        int nBytes = cp737.GetByteCount(greekChars);
        byte[] bytes737 = new byte[nBytes];
        bytes737 = cp737.GetBytes(greekChars);
        // Write the bytes to a file.
        FileStream fs = new FileStream(@".\\CodePageBytes.dat", FileMode.Create);
        fs.Write(bytes737, 0, bytes737.Length);
        fs.Close();

        // Retrieve the byte data from the file.
        fs = new FileStream(@".\\CodePageBytes.dat", FileMode.Open);
        byte[] bytes1 = new byte[fs.Length];
        fs.Read(bytes1, 0, (int)fs.Length);
        fs.Close();

        // Restore the data on a system whose code page is 737.
        string data = cp737.GetString(bytes1);
        Console.WriteLine(data);
        Console.WriteLine();

        // Restore the data on a system whose code page is 1252.
        Encoding cp1252 = Encoding.GetEncoding(1252);
        data = cp1252.GetString(bytes1);
        Console.WriteLine(data);
    }
}

// The example displays the following output:
//       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
//       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

Imports System.IO
Imports System.Text

Module Example
    Public Sub CodePages()
        ' Represent Greek uppercase characters in code page 737.
        Dim greekChars() As Char = {"Α"c, "Β"c, "Γ"c, "Δ"c, "Ε"c, "Ζ"c, "Η"c, "Θ"c,
                                     "Ι"c, "Κ"c, "Λ"c, "Μ"c, "Ν"c, "Ξ"c, "Ο"c, "Π"c,
                                     "Ρ"c, "Σ"c, "Τ"c, "Υ"c, "Φ"c, "Χ"c, "Ψ"c, "Ω"c}

        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)

        Dim cp737 As Encoding = Encoding.GetEncoding(737)
        Dim nBytes As Integer = CInt(cp737.GetByteCount(greekChars))
        Dim bytes737(nBytes - 1) As Byte
        bytes737 = cp737.GetBytes(greekChars)
        ' Write the bytes to a file.
        Dim fs As New FileStream(".\CodePageBytes.dat", FileMode.Create)
        fs.Write(bytes737, 0, bytes737.Length)
        fs.Close()

        ' Retrieve the byte data from the file.
        fs = New FileStream(".\CodePageBytes.dat", FileMode.Open)
        Dim bytes1(CInt(fs.Length - 1)) As Byte
        fs.Read(bytes1, 0, CInt(fs.Length))
        fs.Close()

        ' Restore the data on a system whose code page is 737.
        Dim data As String = cp737.GetString(bytes1)
        Console.WriteLine(data)
        Console.WriteLine()

        ' Restore the data on a system whose code page is 1252.
        Dim cp1252 As Encoding = Encoding.GetEncoding(1252)
        data = cp1252.GetString(bytes1)
        Console.WriteLine(data)
    End Sub
End Module
' The example displays the following output:
'       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
'       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

L’utilisation d’Unicode garantit que les mêmes unités de code correspondent toujours aux mêmes caractères et que les mêmes caractères sont toujours mappés aux mêmes tableaux d’octets.

Utiliser des fichiers de ressources

Même si vous développez une application qui cible une seule culture ou région, vous devez utiliser des fichiers de ressources pour stocker des chaînes et d’autres ressources affichées dans l’interface utilisateur. Vous ne devez jamais les ajouter directement à votre code. L’utilisation de fichiers de ressources présente un certain nombre d’avantages :

  • Toutes les chaînes se trouvent à un même emplacement. Vous n’avez pas besoin de rechercher dans votre code source pour identifier les chaînes à modifier pour une langue ou une culture spécifique.
  • Il n’est pas nécessaire de dupliquer des chaînes. Les développeurs qui n’utilisent pas de fichiers de ressources définissent souvent la même chaîne dans plusieurs fichiers de code source. Cette duplication augmente la probabilité qu’une ou plusieurs instances soient ignorées lorsqu’une chaîne est modifiée.
  • Vous pouvez inclure des ressources non-chaînes, telles que des images ou des données binaires, dans le fichier de ressources au lieu de les stocker dans un fichier autonome distinct, afin qu’elles puissent être récupérées facilement.

L’utilisation de fichiers de ressources présente des avantages particuliers si vous créez une application localisée. Lorsque vous déployez des ressources dans des assemblies satellites, le Common Language Runtime sélectionne automatiquement une ressource appropriée à la culture en fonction de la culture actuelle de l'interface utilisateur, comme définie par la propriété CultureInfo.CurrentUICulture. Tant que vous fournissez une ressource adaptée à la culture et instanciez correctement un objet ResourceManager ou utilisez une classe de ressource fortement typée, l'environnement d'exécution gère les détails de la récupération des ressources appropriées.

Pour plus d’informations sur la création de fichiers de ressources, consultez Création de fichiers de ressources. Pour plus d’informations sur la création et le déploiement d’assemblys satellites, consultez Créer des assemblys satellites et des ressources de package et de déploiement.

Rechercher et comparer des chaînes

Dans la mesure du possible, vous devez gérer des chaînes entières au lieu de les gérer sous forme de série de caractères individuels. Cela est particulièrement important lorsque vous triez ou recherchez des sous-chaînes pour éviter les problèmes associés à l’analyse des caractères combinés.

Conseil / Astuce

Vous pouvez utiliser la StringInfo classe pour utiliser les éléments de texte plutôt que les caractères individuels d’une chaîne.

Dans les recherches et comparaisons de chaînes, une erreur courante consiste à traiter la chaîne comme une collection de caractères, chacune d’elles étant représentée par un Char objet. En fait, un seul caractère peut être formé par un, deux ou plusieurs Char objets. Ces caractères sont trouvés le plus fréquemment dans les chaînes de cultures dont les alphabets se composent de caractères en dehors de la plage de caractères latins de base Unicode (U+0021 à U+007E). L’exemple suivant tente de trouver l’index du caractère LETTRE MAJUSCULE LATINE A avec accent grave (U+00C0) dans une chaîne de caractères. Toutefois, ce caractère peut être représenté de deux façons différentes : en tant qu’unité de code unique (U+00C0) ou en tant que caractère composite (deux unités de code : U+0041 et U+0300). Dans ce cas, le caractère est représenté dans l’instance de chaîne par deux Char objets, U+0041 et U+0300. L'exemple de code appelle les surcharges String.IndexOf(Char) et String.IndexOf(String) pour trouver la position de ce caractère dans la chaîne, mais elles retournent des résultats différents. Le premier appel de méthode a un Char argument ; il effectue une comparaison ordinale et ne peut donc pas trouver de correspondance. Le deuxième appel a un String argument ; il effectue une comparaison sensible à la culture et trouve donc une correspondance.

using System;
using System.Globalization;
using System.Threading;

public class Example17
{
    public static void Main17()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("pl-PL");
        string composite = "\u0041\u0300";
        Console.WriteLine($"Comparing using Char:   {composite.IndexOf('\u00C0')}");
        Console.WriteLine($"Comparing using String: {composite.IndexOf("\u00C0")}");
    }
}

// The example displays the following output:
//       Comparing using Char:   -1
//       Comparing using String: 0
Imports System.Globalization
Imports System.Threading

Module Example17
    Public Sub Main17()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("pl-PL")
        Dim composite As String = ChrW(&H41) + ChrW(&H300)
        Console.WriteLine("Comparing using Char:   {0}", composite.IndexOf(ChrW(&HC0)))
        Console.WriteLine("Comparing using String: {0}", composite.IndexOf(ChrW(&HC0).ToString()))
    End Sub
End Module
' The example displays the following output:
'       Comparing using Char:   -1
'       Comparing using String: 0

Vous pouvez éviter certaines ambiguïtés de cet exemple (appels à deux surcharges similaires d’une méthode retournant des résultats différents) en appelant une surcharge qui inclut un paramètre StringComparison, telles que la méthode String.IndexOf(String, StringComparison) ou String.LastIndexOf(String, StringComparison).

Toutefois, les recherches ne respectent pas toujours la culture. Si l’objectif de la recherche est de prendre une décision de sécurité ou d’autoriser ou de interdire l’accès à une ressource, la comparaison doit être ordinale, comme indiqué dans la section suivante.

Tester l’égalité de chaînes

Si vous souhaitez tester deux chaînes pour l’égalité plutôt que de déterminer comment elles sont comparées dans l’ordre de tri, utilisez la String.Equals méthode au lieu d’une méthode de comparaison de chaînes String.Compare comme ou CompareInfo.Compare.

Les comparaisons pour l’égalité sont généralement effectuées pour accéder à certaines ressources de manière conditionnelle. Par exemple, vous pouvez effectuer une comparaison pour l’égalité pour vérifier un mot de passe ou pour confirmer qu’un fichier existe. Ces comparaisons non linguistiques doivent toujours être ordinales plutôt que sensibles à la culture. En général, vous devez appeler la méthode d’instance String.Equals(String, StringComparison) ou la méthode statique String.Equals(String, String, StringComparison) avec une valeur de StringComparison.Ordinal pour les chaînes telles que des mots de passe, et une valeur de StringComparison.OrdinalIgnoreCase pour les chaînes telles que les noms de fichiers ou les URI.

Les comparaisons pour l’égalité impliquent parfois des recherches ou des comparaisons de sous-chaînes plutôt que des appels à la String.Equals méthode. Dans certains cas, vous pouvez utiliser une recherche de sous-chaîne pour déterminer si cette sous-chaîne est égale à une autre chaîne. Si l’objectif de cette comparaison est non linguistique, la recherche doit également être ordinale plutôt que sensible à la culture.

L’exemple suivant illustre le danger d’une recherche sensible à la culture sur les données non linguistiques. La AccessesFileSystem méthode est conçue pour interdire l’accès au système de fichiers pour les URI commençant par la sous-chaîne « FILE ». Pour cela, elle effectue une comparaison dépendante de la culture et sans respect de la casse entre le début de l’URI et la chaîne « FILE ». Étant donné qu’un URI qui accède au système de fichiers peut commencer par « FILE : » ou « file : », l’hypothèse implicite est que « i » (U+0069) est toujours l’équivalent minuscule de « I » (U+0049). Toutefois, en Turc et en Azerbaïdjan, la version majuscule de « i » est « İ » (U+0130). En raison de cette différence, la comparaison sensible à la culture autorise l’accès au système de fichiers lorsqu’il doit être interdit.

using System;
using System.Globalization;
using System.Threading;

public class Example10
{
    public static void Main10()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR");
        string uri = @"file:\\c:\users\username\Documents\bio.txt";
        if (!AccessesFileSystem(uri))
            // Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.");
        else
            // Prohibit access.
            Console.WriteLine("Access is not allowed.");
    }

    private static bool AccessesFileSystem(string uri)
    {
        return uri.StartsWith("FILE", true, CultureInfo.CurrentCulture);
    }
}

// The example displays the following output:
//         Access is allowed.
Imports System.Globalization
Imports System.Threading

Module Example10
    Public Sub Main10()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR")
        Dim uri As String = "file:\\c:\users\username\Documents\bio.txt"
        If Not AccessesFileSystem(uri) Then
            ' Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.")
        Else
            ' Prohibit access.
            Console.WriteLine("Access is not allowed.")
        End If
    End Sub

    Private Function AccessesFileSystem(uri As String) As Boolean
        Return uri.StartsWith("FILE", True, CultureInfo.CurrentCulture)
    End Function
End Module
' The example displays the following output:
'       Access is allowed.

Vous pouvez éviter ce problème en effectuant une comparaison ordinale qui ignore le cas, comme l’illustre l’exemple suivant.

using System;
using System.Globalization;
using System.Threading;

public class Example11
{
    public static void Main11()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR");
        string uri = @"file:\\c:\users\username\Documents\bio.txt";
        if (!AccessesFileSystem(uri))
            // Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.");
        else
            // Prohibit access.
            Console.WriteLine("Access is not allowed.");
    }

    private static bool AccessesFileSystem(string uri)
    {
        return uri.StartsWith("FILE", StringComparison.OrdinalIgnoreCase);
    }
}

// The example displays the following output:
//         Access is not allowed.
Imports System.Globalization
Imports System.Threading

Module Example11
    Public Sub Main11()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("tr-TR")
        Dim uri As String = "file:\\c:\users\username\Documents\bio.txt"
        If Not AccessesFileSystem(uri) Then
            ' Permit access to resource specified by URI
            Console.WriteLine("Access is allowed.")
        Else
            ' Prohibit access.
            Console.WriteLine("Access is not allowed.")
        End If
    End Sub

    Private Function AccessesFileSystem(uri As String) As Boolean
        Return uri.StartsWith("FILE", StringComparison.OrdinalIgnoreCase)
    End Function
End Module
' The example displays the following output:
'       Access is not allowed.

Ordonner et trier des chaînes

En règle générale, les chaînes ordonnées à afficher dans l’interface utilisateur doivent être triées en fonction de la culture. Dans la plupart des cas, ces comparaisons de chaînes sont gérées implicitement par .NET lorsque vous appelez une méthode qui trie les chaînes, telles que Array.Sort ou List<T>.Sort. Par défaut, les chaînes sont triées à l’aide des conventions de tri de la culture actuelle. L’exemple suivant illustre la différence lorsqu’un tableau de chaînes est trié à l’aide des conventions de la culture anglaise (États-Unis) et de la culture suédoise (Suède).

using System;
using System.Globalization;
using System.Threading;

public class Example18
{
    public static void Main18()
    {
        string[] values = { "able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio" };
        // Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        // Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values);
        string[] enValues = (String[])values.Clone();

        // Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("sv-SE");
        Array.Sort(values);
        string[] svValues = (String[])values.Clone();

        // Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}\n", "Position", "en-US", "sv-SE");
        for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues[ctr], svValues[ctr]);
    }
}

// The example displays the following output:
//       Position en-US           sv-SE
//
//       0        able            able
//       1        Æble            Æble
//       2        ångström        apple
//       3        apple           Windows
//       4        Visual Studio   Visual Studio
//       5        Windows         ångström
Imports System.Globalization
Imports System.Threading

Module Example18
    Public Sub Main18()
        Dim values() As String = {"able", "ångström", "apple",
                                   "Æble", "Windows", "Visual Studio"}
        ' Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        ' Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values)
        Dim enValues() As String = CType(values.Clone(), String())

        ' Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values)
        Dim svValues() As String = CType(values.Clone(), String())

        ' Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}", "Position", "en-US", "sv-SE")
        Console.WriteLine()
        For ctr As Integer = 0 To values.GetUpperBound(0)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues(ctr), svValues(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'       Position en-US           sv-SE
'       
'       0        able            able
'       1        Æble            Æble
'       2        ångström        apple
'       3        apple           Windows
'       4        Visual Studio   Visual Studio
'       5        Windows         ångström

La comparaison de chaînes sensibles à la culture est définie par l'objet CompareInfo, retourné par la propriété CultureInfo.CompareInfo propre à chaque culture. Les comparaisons de chaînes dépendantes de la culture qui utilisent les surcharges de méthode String.Compare utilisent aussi l’objet CompareInfo.

.NET utilise des tables pour effectuer des tris dépendants de la culture sur les données de chaîne. Le contenu de ces tables, qui contiennent des données sur les pondérations de tri et la normalisation des chaînes, est déterminé par la version de la norme Unicode implémentée par une version particulière de .NET. Le tableau suivant répertorie les versions d’Unicode implémentées par les versions spécifiées de .NET. Cette liste de versions Unicode prises en charge s’applique uniquement à la comparaison de caractères et au tri ; elle ne s’applique pas à la classification des caractères Unicode par catégorie. Pour plus d’informations, consultez la section « Strings and The Unicode Standard » dans l’article String .

Version du .NET Framework Système d’exploitation La version Unicode
.NET Framework 2.0 Tous les systèmes d’exploitation Unicode 4.1
.NET Framework 3.0 Tous les systèmes d’exploitation Unicode 4.1
.NET Framework 3.5 Tous les systèmes d’exploitation Unicode 4.1
.NET Framework 4 Tous les systèmes d’exploitation Unicode 5.0
.NET Framework 4.5 et versions ultérieures Windows 7 Unicode 5.0
.NET Framework 4.5 et versions ultérieures Systèmes d’exploitation Windows 8 et versions ultérieures Unicode 6.3.0
.NET Core et .NET 5+ Dépend de la version de la norme Unicode prise en charge par le système d’exploitation sous-jacent.

À compter de .NET Framework 4.5 et dans toutes les versions de .NET Core et .NET 5+, la comparaison de chaînes et le tri dépendent du système d’exploitation. .NET Framework 4.5 et versions ultérieures s’exécutant sur Windows 7 récupère les données de ses propres tables qui implémentent Unicode 5.0. .NET Framework 4.5 et versions ultérieures s’exécutant sur Windows 8 et versions ultérieures récupère les données des tables de système d’exploitation qui implémentent Unicode 6.3. Sur .NET Core et .NET 5+, la version prise en charge d’Unicode dépend du système d’exploitation sous-jacent. Si vous sérialisez des données triées sensibles à la culture, vous pouvez utiliser la SortVersion classe pour déterminer quand vos données sérialisées doivent être triées afin qu’elles soient cohérentes avec .NET et l’ordre de tri du système d’exploitation. Pour obtenir un exemple, consultez le SortVersion sujet de classe.

Si votre application effectue des tris étendus de données de chaîne spécifiques à la culture, vous pouvez utiliser la classe SortKey pour comparer les chaînes. Une clé de tri reflète les pondérations de tri propres à la culture, notamment les pondérations alphabétiques, minuscules et diacritiques d’une chaîne particulière. Étant donné que les comparaisons utilisant des clés de tri sont binaires, elles sont plus rapides que les comparaisons qui utilisent un CompareInfo objet implicitement ou explicitement. Vous créez une clé de tri spécifique à la culture pour une chaîne particulière en passant la chaîne à la méthode CompareInfo.GetSortKey.

L’exemple suivant est similaire à l’exemple précédent. Toutefois, au lieu d’appeler la Array.Sort(Array) méthode, qui appelle implicitement la CompareInfo.Compare méthode, elle définit une System.Collections.Generic.IComparer<T> implémentation qui compare les clés de tri, qu’elle instancie et passe à la Array.Sort<T>(T[], IComparer<T>) méthode.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;

public class SortKeyComparer : IComparer<String>
{
    public int Compare(string? str1, string? str2)
    {
        return (str1, str2) switch
        {
            (null, null) => 0,
            (null, _) => -1,
            (_, null) => 1,
            (var s1, var s2) => SortKey.Compare(
                CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1),
                CultureInfo.CurrentCulture.CompareInfo.GetSortKey(s1))
        };
    }
}

public class Example19
{
    public static void Main19()
    {
        string[] values = { "able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio" };
        SortKeyComparer comparer = new SortKeyComparer();

        // Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        // Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values, comparer);
        string[] enValues = (String[])values.Clone();

        // Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("sv-SE");
        Array.Sort(values, comparer);
        string[] svValues = (String[])values.Clone();

        // Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}\n", "Position", "en-US", "sv-SE");
        for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues[ctr], svValues[ctr]);
    }
}

// The example displays the following output:
//       Position en-US           sv-SE
//
//       0        able            able
//       1        Æble            Æble
//       2        ångström        apple
//       3        apple           Windows
//       4        Visual Studio   Visual Studio
//       5        Windows         ångström
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Threading

Public Class SortKeyComparer : Implements IComparer(Of String)
    Public Function Compare(str1 As String, str2 As String) As Integer _
           Implements IComparer(Of String).Compare
        Dim sk1, sk2 As SortKey
        sk1 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str1)
        sk2 = CultureInfo.CurrentCulture.CompareInfo.GetSortKey(str2)
        Return SortKey.Compare(sk1, sk2)
    End Function
End Class

Module Example19
    Public Sub Main19()
        Dim values() As String = {"able", "ångström", "apple",
                                   "Æble", "Windows", "Visual Studio"}
        Dim comparer As New SortKeyComparer()

        ' Change thread to en-US.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        ' Sort the array and copy it to a new array to preserve the order.
        Array.Sort(values, comparer)
        Dim enValues() As String = CType(values.Clone(), String())

        ' Change culture to Swedish (Sweden).
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values, comparer)
        Dim svValues() As String = CType(values.Clone(), String())

        ' Compare the sorted arrays.
        Console.WriteLine("{0,-8} {1,-15} {2,-15}", "Position", "en-US", "sv-SE")
        Console.WriteLine()
        For ctr As Integer = 0 To values.GetUpperBound(0)
            Console.WriteLine("{0,-8} {1,-15} {2,-15}", ctr, enValues(ctr), svValues(ctr))
        Next
    End Sub
End Module
' The example displays the following output:
'       Position en-US           sv-SE
'       
'       0        able            able
'       1        Æble            Æble
'       2        ångström        apple
'       3        apple           Windows
'       4        Visual Studio   Visual Studio
'       5        Windows         ångström

Éviter la concaténation de chaînes

Si possible, évitez d’utiliser des chaînes composites générées au moment de l’exécution à partir d’expressions concaténées. Les chaînes composites sont difficiles à localiser, car elles supposent souvent un ordre grammatical dans la langue d’origine de l’application qui ne s’applique pas à d’autres langues localisées.

Gérer les dates et les heures

La façon dont vous gérez les valeurs de date et d’heure varie selon qu’elles sont affichées dans l’interface utilisateur ou conservées. Cette section examine les deux utilisations. Il explique également comment gérer les différences de fuseau horaire et les opérations arithmétiques lors de l’utilisation de dates et d’heures.

Afficher les dates et heures

En règle générale, lorsque des dates et des heures sont affichées dans l’interface utilisateur, vous devez utiliser les conventions de mise en forme de la culture de l’utilisateur, définies par la CultureInfo.CurrentCulture propriété et par l’objet DateTimeFormatInfo retourné par la CultureInfo.CurrentCulture.DateTimeFormat propriété. Les conventions de mise en forme de la culture actuelle sont automatiquement utilisées lorsque vous mettez en forme une date à l’aide de l’une des méthodes suivantes :

L’exemple suivant affiche les données du lever du soleil et du coucher du soleil deux fois pour le 11 octobre 2012. Il définit d’abord la culture actuelle en Croate (Croatie), puis en anglais (Royaume-Uni). Dans chaque cas, les dates et heures sont affichées dans le format approprié pour cette culture.

using System;
using System.Globalization;
using System.Threading;

public class Example3
{
    static DateTime[] dates = { new DateTime(2012, 10, 11, 7, 06, 0),
                        new DateTime(2012, 10, 11, 18, 19, 0) };

    public static void Main3()
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR");
        ShowDayInfo();
        Console.WriteLine();
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        ShowDayInfo();
    }

    private static void ShowDayInfo()
    {
        Console.WriteLine($"Date: {dates[0]:D}");
        Console.WriteLine($"   Sunrise: {dates[0]:T}");
        Console.WriteLine($"   Sunset:  {dates[1]:T}");
    }
}

// The example displays the following output:
//       Date: 11. listopada 2012.
//          Sunrise: 7:06:00
//          Sunset:  18:19:00
//
//       Date: 11 October 2012
//          Sunrise: 07:06:00
//          Sunset:  18:19:00
Imports System.Globalization
Imports System.Threading

Module Example3
    Dim dates() As Date = {New Date(2012, 10, 11, 7, 6, 0),
                            New Date(2012, 10, 11, 18, 19, 0)}

    Public Sub Main3()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("hr-HR")
        ShowDayInfo()
        Console.WriteLine()
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        ShowDayInfo()
    End Sub

    Private Sub ShowDayInfo()
        Console.WriteLine("Date: {0:D}", dates(0))
        Console.WriteLine("   Sunrise: {0:T}", dates(0))
        Console.WriteLine("   Sunset:  {0:T}", dates(1))
    End Sub
End Module
' The example displays the following output:
'       Date: 11. listopada 2012.
'          Sunrise: 7:06:00
'          Sunset:  18:19:00
'       
'       Date: 11 October 2012
'          Sunrise: 07:06:00
'          Sunset:  18:19:00

Figer des dates et des heures

Vous ne devez jamais conserver les données de date et d’heure dans un format qui peut varier selon la culture. Il s’agit d’une erreur de programmation courante qui entraîne des données endommagées ou une exception d’exécution. L’exemple suivant sérialise deux dates, le 9 janvier 2013 et le 18 août 2013, sous forme de chaînes à l’aide des conventions de mise en forme de la culture anglaise (États-Unis). Lorsque les données sont récupérées et analysées à l’aide des conventions de la culture anglaise (États-Unis), elles sont correctement restaurées. Toutefois, lorsqu’il est récupéré et analysé à l’aide des conventions de la culture anglaise (Royaume-Uni), la première date est mal interprétée comme le 1er septembre, et la seconde ne parvient pas à analyser car le calendrier grégorien n’a pas de dix-huit mois.

using System;
using System.IO;
using System.Globalization;
using System.Threading;

public class Example4
{
    public static void Main4()
    {
        // Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        DateTime[] dates = { new DateTime(2013, 1, 9),
                           new DateTime(2013, 8, 18) };
        StreamWriter sw = new StreamWriter("dateData.dat");
        sw.Write("{0:d}|{1:d}", dates[0], dates[1]);
        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("dateData.dat");
        string dateData = sr.ReadToEnd();
        sr.Close();
        string[] dateStrings = dateData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {dateStr}");
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {dateStr}");
        }
    }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       The date is Wednesday, January 09, 2013
//       The date is Sunday, August 18, 2013
//
//       Current Culture: English (United Kingdom)
//       The date is 01 September 2013
//       ERROR: Unable to parse 8/18/2013
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example4
    Public Sub Main4()
        ' Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim dates() As DateTime = {New DateTime(2013, 1, 9),
                                    New DateTime(2013, 8, 18)}
        Dim sw As New StreamWriter("dateData.dat")
        sw.Write("{0:d}|{1:d}", dates(0), dates(1))
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("dateData.dat")
        Dim dateData As String = sr.ReadToEnd()
        sr.Close()
        Dim dateStrings() As String = dateData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       Current Culture: English (United States)
'       The date is Wednesday, January 09, 2013
'       The date is Sunday, August 18, 2013
'       
'       Current Culture: English (United Kingdom)
'       The date is 01 September 2013
'       ERROR: Unable to parse 8/18/2013

Vous pouvez éviter ce problème de trois façons :

  • Sérialisez la date et l’heure au format binaire plutôt que sous forme de chaîne.
  • Enregistrez et analysez la représentation sous forme de chaîne de la date et de l’heure à l’aide d’une chaîne de format personnalisée identique, quelle que soit la culture de l’utilisateur.
  • Enregistrez la chaîne en utilisant les conventions de mise en forme de la culture dite indifférente.

L’exemple suivant illustre la dernière approche. Il utilise les conventions de mise en forme de la culture dite indifférente retournée par la propriété statique CultureInfo.InvariantCulture.

using System;
using System.IO;
using System.Globalization;
using System.Threading;

public class Example5
{
    public static void Main5()
    {
        // Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        DateTime[] dates = { new DateTime(2013, 1, 9),
                           new DateTime(2013, 8, 18) };
        StreamWriter sw = new StreamWriter("dateData.dat");
        sw.Write(String.Format(CultureInfo.InvariantCulture,
                               "{0:d}|{1:d}", dates[0], dates[1]));
        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("dateData.dat");
        string dateData = sr.ReadToEnd();
        sr.Close();
        string[] dateStrings = dateData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
                                  DateTimeStyles.None, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {dateStr}");
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, CultureInfo.InvariantCulture,
                                  DateTimeStyles.None, out restoredDate))
                Console.WriteLine($"The date is {restoredDate:D}");
            else
                Console.WriteLine($"ERROR: Unable to parse {dateStr}");
        }
    }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       The date is Wednesday, January 09, 2013
//       The date is Sunday, August 18, 2013
//
//       Current Culture: English (United Kingdom)
//       The date is 09 January 2013
//       The date is 18 August 2013
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example5
    Public Sub Main5()
        ' Persist two dates as strings.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim dates() As DateTime = {New DateTime(2013, 1, 9),
                                    New DateTime(2013, 8, 18)}
        Dim sw As New StreamWriter("dateData.dat")
        sw.Write(String.Format(CultureInfo.InvariantCulture,
                               "{0:d}|{1:d}", dates(0), dates(1)))
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("dateData.dat")
        Dim dateData As String = sr.ReadToEnd()
        sr.Close()
        Dim dateStrings() As String = dateData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
                             DateTimeStyles.None, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each dateStr In dateStrings
            Dim restoredDate As Date
            If Date.TryParse(dateStr, CultureInfo.InvariantCulture,
                             DateTimeStyles.None, restoredDate) Then
                Console.WriteLine("The date is {0:D}", restoredDate)
            Else
                Console.WriteLine("ERROR: Unable to parse {0}", dateStr)
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       Current Culture: English (United States)
'       The date is Wednesday, January 09, 2013
'       The date is Sunday, August 18, 2013
'       
'       Current Culture: English (United Kingdom)
'       The date is 09 January 2013
'       The date is 18 August 2013

Sérialisation et sensibilisation aux fuseaux horaires

Une valeur de date et d’heure peut avoir plusieurs interprétations, allant d’une heure générale (« Les magasins s’ouvrent le 2 janvier 2013, à 9 h 00 A.M. ») à un moment spécifique dans l’heure (« Date de naissance : 2 janvier 2013 6:32:00 A.M. »). Lorsqu’une valeur de temps représente un moment spécifique dans le temps et que vous la restaurez à partir d’une valeur sérialisée, vous devez vous assurer qu’elle représente le même moment dans le temps, quel que soit l’emplacement géographique ou le fuseau horaire de l’utilisateur.

L’exemple suivant illustre ce problème. Il enregistre une valeur de date et d’heure locale unique sous forme de chaîne dans trois formats standard :

  • « G » pour une date générale (heure longue).
  • « s » pour la date/heure triable.
  • « o » pour la date/l’heure d’aller-retour.
using System;
using System.IO;

public class Example6
{
    public static void Main6()
    {
        DateTime dateOriginal = new DateTime(2023, 3, 30, 18, 0, 0);
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local);

        // Serialize a date.
        if (!File.Exists("DateInfo.dat"))
        {
            StreamWriter sw = new StreamWriter("DateInfo.dat");
            sw.Write("{0:G}|{0:s}|{0:o}", dateOriginal);
            sw.Close();
            Console.WriteLine("Serialized dates to DateInfo.dat");
        }
        Console.WriteLine();

        // Restore the date from string values.
        StreamReader sr = new StreamReader("DateInfo.dat");
        string datesToSplit = sr.ReadToEnd();
        string[] dateStrings = datesToSplit.Split('|');
        foreach (var dateStr in dateStrings)
        {
            DateTime newDate = DateTime.Parse(dateStr);
            Console.WriteLine($"'{dateStr}' --> {newDate} {newDate.Kind}");
        }
    }
}
Imports System.IO

Module Example6
    Public Sub Main6()
        ' Serialize a date.
        Dim dateOriginal As Date = #03/30/2023 6:00PM#
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local)
        ' Serialize the date in string form.
        If Not File.Exists("DateInfo.dat") Then
            Dim sw As New StreamWriter("DateInfo.dat")
            sw.Write("{0:G}|{0:s}|{0:o}", dateOriginal)
            sw.Close()
        End If

        ' Restore the date from string values.
        Dim sr As New StreamReader("DateInfo.dat")
        Dim datesToSplit As String = sr.ReadToEnd()
        Dim dateStrings() As String = datesToSplit.Split("|"c)
        For Each dateStr In dateStrings
            Dim newDate As DateTime = DateTime.Parse(dateStr)
            Console.WriteLine("'{0}' --> {1} {2}",
                              dateStr, newDate, newDate.Kind)
        Next
    End Sub
End Module

Lorsque les données sont restaurées sur un système dans le même fuseau horaire que le système sur lequel elle a été sérialisée, les valeurs de date et d’heure désérialisées reflètent avec précision la valeur d’origine, comme le montre la sortie :

'3/30/2013 6:00:00 PM' --> 3/30/2013 6:00:00 PM Unspecified
'2013-03-30T18:00:00' --> 3/30/2013 6:00:00 PM Unspecified
'2013-03-30T18:00:00.0000000-07:00' --> 3/30/2013 6:00:00 PM Local

Toutefois, si vous restaurez les données sur un système dans un autre fuseau horaire, seule la valeur de date et d’heure mise en forme avec la chaîne de format standard « o » (aller-retour) conserve les informations de fuseau horaire et représente donc le même instant dans le temps. Voici la sortie lorsque les données de date et d’heure sont restaurées sur un système dans le fuseau horaire standard romance :

'3/30/2023 6:00:00 PM' --> 3/30/2023 6:00:00 PM Unspecified
'2023-03-30T18:00:00' --> 3/30/2023 6:00:00 PM Unspecified
'2023-03-30T18:00:00.0000000-07:00' --> 3/31/2023 3:00:00 AM Local

Pour refléter avec précision une valeur de date et d’heure qui représente un moment de temps unique, quel que soit le fuseau horaire du système sur lequel les données sont désérialisées, vous pouvez effectuer l’une des opérations suivantes :

  • Enregistrez la valeur sous forme de chaîne à l’aide de la chaîne de format standard « o » (aller-retour). Ensuite, désérialisez-la sur le système cible.
  • Convertissez-le en UTC et enregistrez-le sous forme de chaîne à l’aide de la chaîne de format standard « r » (RFC1123). Désérialisez-le ensuite sur le système cible et convertissez-le en heure locale.
  • Convertissez-le en UTC et enregistrez-le sous forme de chaîne à l’aide de la chaîne de format standard « u » (triable universel). Désérialisez-le ensuite sur le système cible et convertissez-le en heure locale.

L’exemple suivant illustre chaque technique.

using System;
using System.IO;

public class Example9
{
    public static void Main9()
    {
        // Serialize a date.
        DateTime dateOriginal = new DateTime(2023, 3, 30, 18, 0, 0);
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local);

        // Serialize the date in string form.
        if (!File.Exists("DateInfo2.dat"))
        {
            StreamWriter sw = new StreamWriter("DateInfo2.dat");
            sw.Write("{0:o}|{1:r}|{1:u}", dateOriginal,
                                          dateOriginal.ToUniversalTime());
            sw.Close();
        }

        // Restore the date from string values.
        StreamReader sr = new StreamReader("DateInfo2.dat");
        string datesToSplit = sr.ReadToEnd();
        string[] dateStrings = datesToSplit.Split('|');
        for (int ctr = 0; ctr < dateStrings.Length; ctr++)
        {
            DateTime newDate = DateTime.Parse(dateStrings[ctr]);
            if (ctr == 1)
            {
                Console.WriteLine($"'{dateStrings[ctr]}' --> {newDate} {newDate.Kind}");
            }
            else
            {
                DateTime newLocalDate = newDate.ToLocalTime();
                Console.WriteLine($"'{dateStrings[ctr]}' --> {newLocalDate} {newLocalDate.Kind}");
            }
        }
    }
}
Imports System.IO

Module Example9
    Public Sub Main9()
        ' Serialize a date.
        Dim dateOriginal As Date = #03/30/2023 6:00PM#
        dateOriginal = DateTime.SpecifyKind(dateOriginal, DateTimeKind.Local)

        ' Serialize the date in string form.
        If Not File.Exists("DateInfo2.dat") Then
            Dim sw As New StreamWriter("DateInfo2.dat")
            sw.Write("{0:o}|{1:r}|{1:u}", dateOriginal,
                                          dateOriginal.ToUniversalTime())
            sw.Close()
        End If

        ' Restore the date from string values.
        Dim sr As New StreamReader("DateInfo2.dat")
        Dim datesToSplit As String = sr.ReadToEnd()
        Dim dateStrings() As String = datesToSplit.Split("|"c)
        For ctr As Integer = 0 To dateStrings.Length - 1
            Dim newDate As DateTime = DateTime.Parse(dateStrings(ctr))
            If ctr = 1 Then
                Console.WriteLine("'{0}' --> {1} {2}",
                                  dateStrings(ctr), newDate, newDate.Kind)
            Else
                Dim newLocalDate As DateTime = newDate.ToLocalTime()
                Console.WriteLine("'{0}' --> {1} {2}",
                                  dateStrings(ctr), newLocalDate, newLocalDate.Kind)
            End If
        Next
    End Sub
End Module

Lorsque les données sont sérialisées sur un système dans le fuseau horaire standard du Pacifique et désérialisées sur un système dans le fuseau horaire standard romance, l’exemple affiche la sortie suivante :

'2023-03-30T18:00:00.0000000-07:00' --> 3/31/2023 3:00:00 AM Local
'Sun, 31 Mar 2023 01:00:00 GMT' --> 3/31/2023 3:00:00 AM Local
'2023-03-31 01:00:00Z' --> 3/31/2023 3:00:00 AM Local

Pour plus d’informations, consultez Convertir des heures entre les fuseaux horaires.

Effectuer une arithmétique de date et d’heure

Tant les types DateTime que les types DateTimeOffset prennent en charge les opérations arithmétiques. Vous pouvez calculer la différence entre deux valeurs de date, ou vous pouvez ajouter ou soustraire des intervalles de temps particuliers à ou à partir d’une valeur de date. Toutefois, les opérations arithmétiques sur les valeurs de date et d’heure ne prennent pas en compte les fuseaux horaires et les règles d’ajustement des fuseaux horaires. En raison de cela, l’arithmétique de date et d’heure sur les valeurs qui représentent des moments dans le temps peut retourner des résultats incorrects.

Par exemple, la transition de l’heure d’été du Pacifique à l’heure d’été se produit le deuxième dimanche de mars, qui est le 10 mars pour l’année 2013. Comme l’illustre l’exemple suivant, si vous calculez la date et l’heure comprises entre 48 heures après le 9 mars 2013 à 10 h 30 sur un système du fuseau horaire standard du Pacifique, le résultat, le 11 mars 2013 à 10 h 30, ne prend pas en compte l’ajustement du temps intermédiaire.

using System;

public class Example7
{
    public static void Main7()
    {
        DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
                                              DateTimeKind.Local);
        TimeSpan interval = new TimeSpan(48, 0, 0);
        DateTime date2 = date1 + interval;
        Console.WriteLine($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
    }
}

// The example displays the following output:
//        3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM
Module Example7
    Public Sub Main7()
        Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
                                                 DateTimeKind.Local)
        Dim interval As New TimeSpan(48, 0, 0)
        Dim date2 As Date = date1 + interval
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2)
    End Sub
End Module
' The example displays the following output:
'       3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 10:30 AM

Pour vous assurer qu’une opération arithmétique sur les valeurs de date et d’heure produit des résultats précis, procédez comme suit :

  1. Convertissez l’heure dans le fuseau horaire source en UTC.
  2. Effectuez l’opération arithmétique.
  3. Si le résultat est une valeur de date et d’heure, convertissez-la d’UTC en heure dans le fuseau horaire source.

L’exemple suivant est similaire à l’exemple précédent, sauf qu’il suit ces trois étapes pour ajouter correctement 48 heures au 9 mars 2013 à 10:30 A.M.

using System;

public class Example8
{
    public static void Main8()
    {
        TimeZoneInfo pst = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
        DateTime date1 = DateTime.SpecifyKind(new DateTime(2013, 3, 9, 10, 30, 0),
                                              DateTimeKind.Local);
        DateTime utc1 = date1.ToUniversalTime();
        TimeSpan interval = new TimeSpan(48, 0, 0);
        DateTime utc2 = utc1 + interval;
        DateTime date2 = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst);
        Console.WriteLine($"{date1:g} + {interval.TotalHours:N1} hours = {date2:g}");
    }
}

// The example displays the following output:
//        3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM
Module Example8
    Public Sub Main8()
        Dim pst As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
        Dim date1 As Date = DateTime.SpecifyKind(#3/9/2013 10:30AM#,
                                                 DateTimeKind.Local)
        Dim utc1 As Date = date1.ToUniversalTime()
        Dim interval As New TimeSpan(48, 0, 0)
        Dim utc2 As Date = utc1 + interval
        Dim date2 As Date = TimeZoneInfo.ConvertTimeFromUtc(utc2, pst)
        Console.WriteLine("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2)
    End Sub
End Module
' The example displays the following output:
'       3/9/2013 10:30 AM + 48.0 hours = 3/11/2013 11:30 AM

Pour plus d’informations, consultez Effectuer des opérations arithmétiques avec des dates et des heures.

Utiliser des noms sensibles à la culture pour les éléments de date

Votre application peut avoir besoin d’afficher le nom du mois ou le jour de la semaine. Pour ce faire, le code suivant est courant.

using System;

public class Example12
{
   public static void Main12()
   {
      DateTime midYear = new DateTime(2013, 7, 1);
      Console.WriteLine($"{midYear:d} is a {GetDayName(midYear)}.");
   }

   private static string GetDayName(DateTime date)
   {
      return date.DayOfWeek.ToString("G");
   }
}

// The example displays the following output:
//        7/1/2013 is a Monday.
Module Example12
    Public Sub Main12()
        Dim midYear As Date = #07/01/2013#
        Console.WriteLine("{0:d} is a {1}.", midYear, GetDayName(midYear))
    End Sub

    Private Function GetDayName(dat As Date) As String
        Return dat.DayOfWeek.ToString("G")
    End Function
End Module
' The example displays the following output:
'       7/1/2013 is a Monday.

Toutefois, ce code retourne toujours les noms des jours de la semaine en anglais. Le code qui extrait le nom du mois est souvent encore plus rigide. Il suppose fréquemment un calendrier de douze mois avec des noms de mois dans une langue spécifique.

En utilisant des chaînes de format de date et d’heure personnalisées ou les propriétés de l’objet DateTimeFormatInfo , il est facile d’extraire des chaînes qui reflètent les noms des jours de la semaine ou des mois dans la culture de l’utilisateur, comme l’illustre l’exemple suivant. Il modifie la culture actuelle en français (France) et affiche le nom du jour de la semaine et le nom du mois pour le 1er juillet 2013.

using System;
using System.Globalization;

public class Example13
{
    public static void Main13()
    {
        // Set the current culture to French (France).
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");

        DateTime midYear = new DateTime(2013, 7, 1);
        Console.WriteLine($"{midYear:d} is a {DateUtilities.GetDayName(midYear)}.");
        Console.WriteLine($"{midYear:d} is a {DateUtilities.GetDayName((int)midYear.DayOfWeek)}.");
        Console.WriteLine($"{midYear:d} is in {DateUtilities.GetMonthName(midYear)}.");
        Console.WriteLine($"{midYear:d} is in {DateUtilities.GetMonthName(midYear.Month)}.");
    }
}

public static class DateUtilities
{
    public static string GetDayName(int dayOfWeek)
    {
        if (dayOfWeek < 0 | dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length)
            return String.Empty;
        else
            return DateTimeFormatInfo.CurrentInfo.DayNames[dayOfWeek];
    }

    public static string GetDayName(DateTime date)
    {
        return date.ToString("dddd");
    }

    public static string GetMonthName(int month)
    {
        if (month < 1 | month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1)
            return String.Empty;
        else
            return DateTimeFormatInfo.CurrentInfo.MonthNames[month - 1];
    }

    public static string GetMonthName(DateTime date)
    {
        return date.ToString("MMMM");
    }
}

// The example displays the following output:
//       01/07/2013 is a lundi.
//       01/07/2013 is a lundi.
//       01/07/2013 is in juillet.
//       01/07/2013 is in juillet.
Imports System.Globalization
Imports System.Threading

Module Example13
    Public Sub Main13()
        ' Set the current culture to French (France).
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")

        Dim midYear As Date = #07/01/2013#
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear))
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear.DayOfWeek))
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear))
        Console.WriteLine("{0:d} is in {1}.", midYear, DateUtilities.GetMonthName(midYear.Month))
    End Sub
End Module

Public Class DateUtilities
    Public Shared Function GetDayName(dayOfWeek As Integer) As String
        If dayOfWeek < 0 Or dayOfWeek > DateTimeFormatInfo.CurrentInfo.DayNames.Length Then
            Return String.Empty
        Else
            Return DateTimeFormatInfo.CurrentInfo.DayNames(dayOfWeek)
        End If
    End Function

    Public Shared Function GetDayName(dat As Date) As String
        Return dat.ToString("dddd")
    End Function

    Public Shared Function GetMonthName(month As Integer) As String
        If month < 1 Or month > DateTimeFormatInfo.CurrentInfo.MonthNames.Length - 1 Then
            Return String.Empty
        Else
            Return DateTimeFormatInfo.CurrentInfo.MonthNames(month - 1)
        End If
    End Function

    Public Shared Function GetMonthName(dat As Date) As String
        Return dat.ToString("MMMM")
    End Function
End Class
' The example displays the following output:
'       01/07/2013 is a lundi.
'       01/07/2013 is a lundi.
'       01/07/2013 is in juillet.
'       01/07/2013 is in juillet.

Valeurs numériques

La gestion des nombres varie selon qu’ils sont affichés dans l’interface utilisateur ou conservés. Cette section examine les deux utilisations.

Remarque

Dans les opérations d’analyse et de mise en forme, .NET reconnaît uniquement les caractères latins de base 0 à 9 (U+0030 à U+0039) comme chiffres numériques.

Afficher des valeurs numériques

En règle générale, lorsque des nombres sont affichés dans l’interface utilisateur, vous devez utiliser les conventions de mise en forme de la culture de l’utilisateur, qui est définie par la CultureInfo.CurrentCulture propriété et par l’objet NumberFormatInfo retourné par la CultureInfo.CurrentCulture.NumberFormat propriété. Les conventions de mise en forme de la culture actuelle sont automatiquement utilisées lorsque vous mettez en forme une date de la manière suivante :

  • En utilisant la méthode sans paramètre ToString pour tout type numérique.
  • L'utilisation de la méthode ToString(String) de n’importe quel type numérique, incluant une chaîne de format comme argument.
  • Utilisation de la mise en forme composite avec des valeurs numériques.

L’exemple suivant affiche la température moyenne par mois à Paris, En France. Elle définit d’abord la culture actuelle en français (France) avant d’afficher les données, puis la définit en anglais (États-Unis). Dans chaque cas, les noms et les températures du mois sont affichés dans le format approprié pour cette culture. Notez que les deux cultures utilisent des séparateurs décimaux différents dans la valeur de température. Notez également que l’exemple utilise la chaîne de format de date et d’heure personnalisée « MMMM » pour afficher le nom complet du mois et qu’il alloue l’espace approprié pour le nom du mois dans la chaîne de résultats en déterminant la longueur du nom du mois le plus long dans le DateTimeFormatInfo.MonthNames tableau.

using System;
using System.Globalization;
using System.Threading;

public class Example14
{
    public static void Main14()
    {
        DateTime dateForMonth = new DateTime(2013, 1, 1);
        double[] temperatures = {  3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
                                19.9, 18.2, 15.9, 11.3, 6.9, 5.3 };

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
        // Build the format string dynamically so we allocate enough space for the month name.
        string fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}";
        for (int ctr = 0; ctr < temperatures.Length; ctr++)
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures[ctr]);

        Console.WriteLine();

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
        fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}";
        for (int ctr = 0; ctr < temperatures.Length; ctr++)
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures[ctr]);
    }

    private static int GetLongestMonthNameLength()
    {
        int length = 0;
        foreach (var nameOfMonth in DateTimeFormatInfo.CurrentInfo.MonthNames)
            if (nameOfMonth.Length > length) length = nameOfMonth.Length;

        return length;
    }
}

// The example displays the following output:
//    Current Culture: French (France)
//       janvier        3,4
//       février        3,5
//       mars           7,6
//       avril         10,4
//       mai           14,5
//       juin          17,2
//       juillet       19,9
//       août          18,2
//       septembre     15,9
//       octobre       11,3
//       novembre       6,9
//       décembre       5,3
//
//       Current Culture: English (United States)
//       January        3.4
//       February       3.5
//       March          7.6
//       April         10.4
//       May           14.5
//       June          17.2
//       July          19.9
//       August        18.2
//       September     15.9
//       October       11.3
//       November       6.9
//       December       5.3
Imports System.Globalization
Imports System.Threading

Module Example14
    Public Sub Main14()
        Dim dateForMonth As Date = #1/1/2013#
        Dim temperatures() As Double = {3.4, 3.5, 7.6, 10.4, 14.5, 17.2,
                                         19.9, 18.2, 15.9, 11.3, 6.9, 5.3}

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Dim fmtString As String = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}"
        For ctr = 0 To temperatures.Length - 1
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures(ctr))
        Next
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        ' Build the format string dynamically so we allocate enough space for the month name.
        fmtString = "{0,-" + GetLongestMonthNameLength().ToString() + ":MMMM}     {1,4}"
        For ctr = 0 To temperatures.Length - 1
            Console.WriteLine(fmtString,
                              dateForMonth.AddMonths(ctr),
                              temperatures(ctr))
        Next
    End Sub

    Private Function GetLongestMonthNameLength() As Integer
        Dim length As Integer
        For Each nameOfMonth In DateTimeFormatInfo.CurrentInfo.MonthNames
            If nameOfMonth.Length > length Then length = nameOfMonth.Length
        Next
        Return length
    End Function
End Module
' The example displays the following output:
'       Current Culture: French (France)
'       janvier        3,4
'       février        3,5
'       mars           7,6
'       avril         10,4
'       mai           14,5
'       juin          17,2
'       juillet       19,9
'       août          18,2
'       septembre     15,9
'       octobre       11,3
'       novembre       6,9
'       décembre       5,3
'       
'       Current Culture: English (United States)
'       January        3.4
'       February       3.5
'       March          7.6
'       April         10.4
'       May           14.5
'       June          17.2
'       July          19.9
'       August        18.2
'       September     15.9
'       October       11.3
'       November       6.9
'       December       5.3

Conserver les valeurs numériques

Vous ne devez jamais conserver les données numériques dans un format spécifique à la culture. Il s’agit d’une erreur de programmation courante qui entraîne des données endommagées ou une exception d’exécution. L’exemple suivant génère dix nombres à virgule flottante aléatoire, puis les sérialise en tant que chaînes à l’aide des conventions de mise en forme de la culture anglaise (États-Unis). Lorsque les données sont récupérées et analysées à l’aide des conventions de la culture anglaise (États-Unis), elles sont correctement restaurées. Toutefois, lorsqu’elle est récupérée et analysée à l’aide des conventions de la culture française (France), aucun des nombres ne peut être analysé, car les cultures utilisent des séparateurs décimaux différents.

using System;
using System.Globalization;
using System.IO;
using System.Threading;

public class Example15
{
    public static void Main15()
    {
        // Create ten random doubles.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        double[] numbers = GetRandomNumbers(10);
        DisplayRandomNumbers(numbers);

        // Persist the numbers as strings.
        StreamWriter sw = new StreamWriter("randoms.dat");
        for (int ctr = 0; ctr < numbers.Length; ctr++)
            sw.Write("{0:R}{1}", numbers[ctr], ctr < numbers.Length - 1 ? "|" : "");

        sw.Close();

        // Read the persisted data.
        StreamReader sr = new StreamReader("randoms.dat");
        string numericData = sr.ReadToEnd();
        sr.Close();
        string[] numberStrings = numericData.Split('|');

        // Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var numberStr in numberStrings)
        {
            double restoredNumber;
            if (Double.TryParse(numberStr, out restoredNumber))
                Console.WriteLine(restoredNumber.ToString("R"));
            else
                Console.WriteLine($"ERROR: Unable to parse '{numberStr}'");
        }
        Console.WriteLine();

        // Restore and display the data using the conventions of the fr-FR culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR");
        Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
        foreach (var numberStr in numberStrings)
        {
            double restoredNumber;
            if (Double.TryParse(numberStr, out restoredNumber))
                Console.WriteLine(restoredNumber.ToString("R"));
            else
                Console.WriteLine($"ERROR: Unable to parse '{numberStr}'");
        }
    }

    private static double[] GetRandomNumbers(int n)
    {
        Random rnd = new Random();
        double[] numbers = new double[n];
        for (int ctr = 0; ctr < n; ctr++)
            numbers[ctr] = rnd.NextDouble() * 1000;
        return numbers;
    }

    private static void DisplayRandomNumbers(double[] numbers)
    {
        for (int ctr = 0; ctr < numbers.Length; ctr++)
            Console.WriteLine(numbers[ctr].ToString("R"));
        Console.WriteLine();
    }
}

// The example displays output like the following:
//       487.0313743534644
//       674.12000879371533
//       498.72077885024288
//       42.3034229512808
//       970.57311049223563
//       531.33717716268131
//       587.82905693530529
//       562.25210175023039
//       600.7711019370571
//       299.46113717717174
//
//       Current Culture: English (United States)
//       487.0313743534644
//       674.12000879371533
//       498.72077885024288
//       42.3034229512808
//       970.57311049223563
//       531.33717716268131
//       587.82905693530529
//       562.25210175023039
//       600.7711019370571
//       299.46113717717174
//
//       Current Culture: French (France)
//       ERROR: Unable to parse '487.0313743534644'
//       ERROR: Unable to parse '674.12000879371533'
//       ERROR: Unable to parse '498.72077885024288'
//       ERROR: Unable to parse '42.3034229512808'
//       ERROR: Unable to parse '970.57311049223563'
//       ERROR: Unable to parse '531.33717716268131'
//       ERROR: Unable to parse '587.82905693530529'
//       ERROR: Unable to parse '562.25210175023039'
//       ERROR: Unable to parse '600.7711019370571'
//       ERROR: Unable to parse '299.46113717717174'
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example15
    Public Sub Main15()
        ' Create ten random doubles.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim numbers() As Double = GetRandomNumbers(10)
        DisplayRandomNumbers(numbers)

        ' Persist the numbers as strings.
        Dim sw As New StreamWriter("randoms.dat")
        For ctr As Integer = 0 To numbers.Length - 1
            sw.Write("{0:R}{1}", numbers(ctr), If(ctr < numbers.Length - 1, "|", ""))
        Next
        sw.Close()

        ' Read the persisted data.
        Dim sr As New StreamReader("randoms.dat")
        Dim numericData As String = sr.ReadToEnd()
        sr.Close()
        Dim numberStrings() As String = numericData.Split("|"c)

        ' Restore and display the data using the conventions of the en-US culture.
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each numberStr In numberStrings
            Dim restoredNumber As Double
            If Double.TryParse(numberStr, restoredNumber) Then
                Console.WriteLine(restoredNumber.ToString("R"))
            Else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
            End If
        Next
        Console.WriteLine()

        ' Restore and display the data using the conventions of the fr-FR culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-FR")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        For Each numberStr In numberStrings
            Dim restoredNumber As Double
            If Double.TryParse(numberStr, restoredNumber) Then
                Console.WriteLine(restoredNumber.ToString("R"))
            Else
                Console.WriteLine("ERROR: Unable to parse '{0}'", numberStr)
            End If
        Next
    End Sub

    Private Function GetRandomNumbers(n As Integer) As Double()
        Dim rnd As New Random()
        Dim numbers(n - 1) As Double
        For ctr As Integer = 0 To n - 1
            numbers(ctr) = rnd.NextDouble * 1000
        Next
        Return numbers
    End Function

    Private Sub DisplayRandomNumbers(numbers As Double())
        For ctr As Integer = 0 To numbers.Length - 1
            Console.WriteLine(numbers(ctr).ToString("R"))
        Next
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       487.0313743534644
'       674.12000879371533
'       498.72077885024288
'       42.3034229512808
'       970.57311049223563
'       531.33717716268131
'       587.82905693530529
'       562.25210175023039
'       600.7711019370571
'       299.46113717717174
'       
'       Current Culture: English (United States)
'       487.0313743534644
'       674.12000879371533
'       498.72077885024288
'       42.3034229512808
'       970.57311049223563
'       531.33717716268131
'       587.82905693530529
'       562.25210175023039
'       600.7711019370571
'       299.46113717717174
'       
'       Current Culture: French (France)
'       ERROR: Unable to parse '487.0313743534644'
'       ERROR: Unable to parse '674.12000879371533'
'       ERROR: Unable to parse '498.72077885024288'
'       ERROR: Unable to parse '42.3034229512808'
'       ERROR: Unable to parse '970.57311049223563'
'       ERROR: Unable to parse '531.33717716268131'
'       ERROR: Unable to parse '587.82905693530529'
'       ERROR: Unable to parse '562.25210175023039'
'       ERROR: Unable to parse '600.7711019370571'
'       ERROR: Unable to parse '299.46113717717174'

Pour éviter ce problème, vous pouvez utiliser l’une des techniques suivantes :

  • Enregistrez et analysez la représentation sous forme de chaîne du nombre à l’aide d’une chaîne de format personnalisée identique, quelle que soit la culture de l’utilisateur.
  • Enregistrez le nombre sous forme de chaîne à l’aide des conventions de mise en forme de la culture invariante, qui est retournée par la CultureInfo.InvariantCulture propriété.

La sérialisation des valeurs monétaires est un cas particulier. Étant donné qu’une valeur monétaire dépend de l’unité de devise dans laquelle elle est exprimée, il est peu judicieux de le traiter comme une valeur numérique indépendante. Toutefois, si vous enregistrez une valeur monétaire sous la forme d’une chaîne mise en forme qui inclut un symbole monétaire, elle ne peut pas être désérialisée sur un système dont la culture par défaut utilise un autre symbole monétaire, comme l’illustre l’exemple suivant.

using System;
using System.Globalization;
using System.IO;
using System.Threading;

public class Example1
{
   public static void Main1()
   {
      // Display the currency value.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
      Decimal value = 16039.47m;
      Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
      Console.WriteLine($"Currency Value: {value:C2}");

      // Persist the currency value as a string.
      StreamWriter sw = new StreamWriter("currency.dat");
      sw.Write(value.ToString("C2"));
      sw.Close();

      // Read the persisted data using the current culture.
      StreamReader sr = new StreamReader("currency.dat");
      string currencyData = sr.ReadToEnd();
      sr.Close();

      // Restore and display the data using the conventions of the current culture.
      Decimal restoredValue;
      if (Decimal.TryParse(currencyData, out restoredValue))
         Console.WriteLine(restoredValue.ToString("C2"));
      else
         Console.WriteLine($"ERROR: Unable to parse '{currencyData}'");
      Console.WriteLine();

      // Restore and display the data using the conventions of the en-GB culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
      Console.WriteLine($"Current Culture: {Thread.CurrentThread.CurrentCulture.DisplayName}");
      if (Decimal.TryParse(currencyData, NumberStyles.Currency, null, out restoredValue))
         Console.WriteLine(restoredValue.ToString("C2"));
      else
         Console.WriteLine($"ERROR: Unable to parse '{currencyData}'");
      Console.WriteLine();
   }
}
// The example displays output like the following:
//       Current Culture: English (United States)
//       Currency Value: $16,039.47
//       ERROR: Unable to parse '$16,039.47'
//
//       Current Culture: English (United Kingdom)
//       ERROR: Unable to parse '$16,039.47'
Imports System.Globalization
Imports System.IO
Imports System.Threading

Module Example1
    Public Sub Main1()
        ' Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim value As Decimal = 16039.47D
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Console.WriteLine("Currency Value: {0:C2}", value)

        ' Persist the currency value as a string.
        Dim sw As New StreamWriter("currency.dat")
        sw.Write(value.ToString("C2"))
        sw.Close()

        ' Read the persisted data using the current culture.
        Dim sr As New StreamReader("currency.dat")
        Dim currencyData As String = sr.ReadToEnd()
        sr.Close()

        ' Restore and display the data using the conventions of the current culture.
        Dim restoredValue As Decimal
        If Decimal.TryParse(currencyData, restoredValue) Then
            Console.WriteLine(restoredValue.ToString("C2"))
        Else
            Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
        End If
        Console.WriteLine()

        ' Restore and display the data using the conventions of the en-GB culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        If Decimal.TryParse(currencyData, NumberStyles.Currency, Nothing, restoredValue) Then
            Console.WriteLine(restoredValue.ToString("C2"))
        Else
            Console.WriteLine("ERROR: Unable to parse '{0}'", currencyData)
        End If
        Console.WriteLine()
    End Sub
End Module
' The example displays output like the following:
'       Current Culture: English (United States)
'       Currency Value: $16,039.47
'       ERROR: Unable to parse '$16,039.47'
'       
'       Current Culture: English (United Kingdom)
'       ERROR: Unable to parse '$16,039.47'

Au lieu de cela, vous devez sérialiser la valeur numérique avec certaines informations culturelles, telles que le nom de la culture, afin que la valeur et son symbole monétaire puissent être désérialisés indépendamment de la culture actuelle. L’exemple suivant fait cela en définissant une CurrencyValue structure avec deux membres : la Decimal valeur et le nom de la culture à laquelle appartient la valeur.

using System;
using System.Globalization;
using System.Text.Json;
using System.Threading;

public class Example2
{
    public static void Main2()
    {
        // Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US");
        Decimal value = 16039.47m;
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");
        Console.WriteLine($"Currency Value: {value:C2}");

        // Serialize the currency data.
        CurrencyValue data = new()
        {
            Amount = value,
            CultureName = CultureInfo.CurrentCulture.Name
        };
        string serialized = JsonSerializer.Serialize(data);
        Console.WriteLine();

        // Change the current culture.
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
        Console.WriteLine($"Current Culture: {CultureInfo.CurrentCulture.DisplayName}");

        // Deserialize the data.
        CurrencyValue restoredData = JsonSerializer.Deserialize<CurrencyValue>(serialized);

        // Display the round-tripped value.
        CultureInfo culture = CultureInfo.CreateSpecificCulture(restoredData.CultureName);
        Console.WriteLine($"Currency Value: {restoredData.Amount.ToString("C2", culture)}");
    }
}

internal struct CurrencyValue
{
    public decimal Amount { get; set; }
    public string CultureName { get; set; }
}

// The example displays the following output:
//       Current Culture: English (United States)
//       Currency Value: $16,039.47
//
//       Current Culture: English (United Kingdom)
//       Currency Value: $16,039.47
Imports System.Globalization
Imports System.Text.Json
Imports System.Threading

Friend Structure CurrencyValue
    Public Property Amount As Decimal
    Public Property CultureName As String
End Structure

Module Example2
    Public Sub Main2()
        ' Display the currency value.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-US")
        Dim value As Decimal = 16039.47D
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)
        Console.WriteLine("Currency Value: {0:C2}", value)

        ' Serialize the currency data.
        Dim data As New CurrencyValue With {
            .Amount = value,
            .CultureName = CultureInfo.CurrentCulture.Name
        }

        Dim serialized As String = JsonSerializer.Serialize(data)
        Console.WriteLine()

        ' Change the current culture.
        CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB")
        Console.WriteLine("Current Culture: {0}", CultureInfo.CurrentCulture.DisplayName)

        ' Deserialize the data.
        Dim restoredData As CurrencyValue = JsonSerializer.Deserialize(Of CurrencyValue)(serialized)

        ' Display the round-tripped value.
        Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture(restoredData.CultureName)
        Console.WriteLine("Currency Value: {0}", restoredData.Amount.ToString("C2", culture))
    End Sub
End Module

' The example displays the following output:
'       Current Culture: English (United States)
'       Currency Value: $16,039.47
'       
'       Current Culture: English (United Kingdom)
'       Currency Value: $16,039.47

Utiliser des paramètres spécifiques à la culture

Dans .NET, la CultureInfo classe représente une culture ou une région particulière. Certaines de ses propriétés renvoient des objets qui fournissent des informations spécifiques sur un aspect d’une culture.

En général, n’effectuez aucune hypothèse sur les valeurs des propriétés spécifiques CultureInfo et de leurs objets associés. Au lieu de cela, vous devez afficher les données propres à la culture en tant que sujet à modification, pour ces raisons :

  • Les valeurs de propriété individuelles sont soumises à des modifications et à une révision au fil du temps, à mesure que les données sont corrigées, de meilleures données deviennent disponibles ou des conventions propres à la culture changent.

  • Les valeurs de propriété individuelles peuvent varier entre les versions de .NET ou de système d’exploitation.

  • .NET prend en charge les cultures de remplacement. Cela permet de définir une nouvelle culture personnalisée qui complète les cultures standard existantes ou remplace complètement une culture standard existante.

  • Sur les systèmes Windows, l’utilisateur peut personnaliser des paramètres propres à la culture à l’aide de l’application Région et Langue dans le Panneau de configuration. Lorsque vous instanciez un CultureInfo objet, vous pouvez déterminer s’il reflète ces personnalisations utilisateur en appelant le CultureInfo(String, Boolean) constructeur. En règle générale, pour les applications de l’utilisateur final, vous devez respecter les préférences utilisateur afin que l’utilisateur soit présenté avec des données dans un format qu’il attend.

Voir aussi