Globalizace

Globalizace zahrnuje navrhování a vývoj aplikací připravených pro svět, která podporuje lokalizovaná rozhraní a regionální data pro uživatele ve více kulturách. Než začnete s fází návrhu, měli byste určit, které jazykové verze bude vaše aplikace podporovat. I když aplikace cílí na jednu jazykovou verzi nebo oblast jako výchozí, můžete ji navrhnout a napsat, aby ji bylo možné snadno rozšířit na uživatele v jiných jazykových verzích nebo oblastech.

Jako vývojáři máme všechny předpoklady o uživatelských rozhraních a datech, které tvoří naše jazykové verze. Například pro anglickou vývojáře v USA, serializace dat data a času jako řetězec ve formátu MM/dd/yyyy hh:mm:ss vypadá dokonale rozumně. Deserializace řetězce v systému v jiné jazykové verzi však pravděpodobně vyvolá FormatException výjimku nebo vytvoří nepřesná data. Globalizace nám umožňuje identifikovat takové předpoklady specifické pro jazykovou verzi a zajistit, aby neměly vliv na návrh nebo kód naší aplikace.

Tento článek popisuje některé hlavní problémy, které byste měli zvážit, a osvědčené postupy, které můžete dodržovat při zpracování řetězců, hodnot data a času a číselných hodnot v globalizované aplikaci.

Řetězce

Zpracování znaků a řetězců je ústředním cílem globalizace, protože každá jazyková verze nebo oblast může používat různé znaky a znakové sady a řadit je jinak. Tato část obsahuje doporučení pro používání řetězců v globalizovaných aplikacích.

Interní použití unicode

Ve výchozím nastavení používá .NET řetězce Unicode. Řetězec Unicode se skládá z nuly, jednoho nebo více Char objektů, z nichž každý představuje jednotku kódu UTF-16. Existuje reprezentace Unicode pro téměř každý znak v každé znakové sadě používané po celém světě.

Mnoho aplikací a operačních systémů, včetně operačního systému Windows, může také používat znakové stránky k reprezentaci znakových sad. Kódové stránky obvykle obsahují standardní hodnoty ASCII z 0x00 prostřednictvím 0x7F a mapují další znaky na zbývající hodnoty z 0x80 až 0xFF. Interpretace hodnot z 0x80 až 0xFF závisí na konkrétní znakové stránce. Z tohoto důvodu byste se měli vyhnout použití znakových stránek v globalizované aplikaci, pokud je to možné.

Následující příklad ukazuje nebezpečí interpretace dat znakové stránky, když se výchozí znaková stránka v systému liší od znakové stránky, na které byla data uložena. (Pro simulaci tohoto scénáře příklad explicitně určuje různé znakové stránky.) Nejprve příklad definuje pole, které se skládá z velkých znaků řecké abecedy. Kóduje je do bajtového pole pomocí znakové stránky 737 (označované také jako MS-DOS Řečtina) a uloží bajtové pole do souboru. Pokud se soubor načte a jeho bajtové pole je dekódováno pomocí znakové stránky 737, původní znaky se obnoví. Pokud je však soubor načten a jeho bajtové pole je dekódováno pomocí znakové stránky 1252 (nebo Windows-1252, který představuje znaky v latince), původní znaky budou ztraceny.

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:
'       ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
'       €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—

Použití sady Unicode zajišťuje, že stejné jednotky kódu se vždy mapují na stejné znaky a že stejné znaky se vždy mapují na stejná pole bajtů.

Použití souborů prostředků

I když vyvíjíte aplikaci, která cílí na jednu jazykovou verzi nebo oblast, měli byste použít soubory prostředků k ukládání řetězců a dalších prostředků, které se zobrazují v uživatelském rozhraní. Nikdy byste je neměli přidávat přímo do kódu. Použití souborů prostředků má řadu výhod:

  • Všechny řetězce jsou v jednom umístění. Nemusíte hledat ve zdrojovém kódu, abyste identifikovali řetězce, které se mají upravit pro určitý jazyk nebo jazykovou verzi.
  • Není nutné duplikovat řetězce. Vývojáři, kteří nepoužívají soubory prostředků, často definují stejný řetězec ve více souborech zdrojového kódu. Tato duplicita zvyšuje pravděpodobnost, že při změně řetězce bude jedna nebo více instancí přehlédnuto.
  • Do souboru prostředků můžete zahrnout jiné než řetězcové prostředky, jako jsou obrázky nebo binární data, místo abyste je ukládali do samostatného samostatného souboru, aby je bylo možné snadno načíst.

Použití souborů prostředků má určité výhody, pokud vytváříte lokalizovanou aplikaci. Když nasadíte prostředky do satelitních sestavení, modul CLR (Common Language Runtime) automaticky vybere prostředek odpovídající jazykové verzi na základě aktuální jazykové verze uživatelského rozhraní uživatele, jak je definováno vlastností CultureInfo.CurrentUICulture . Pokud zadáte odpovídající prostředek specifický pro jazykovou verzi a správně vytvoří instanci objektu ResourceManager nebo použijete třídu prostředků silného typu, modul runtime zpracuje podrobnosti o načtení příslušných prostředků.

Další informace o vytváření souborů prostředků naleznete v tématu Vytváření souborů prostředků. Informace o vytváření a nasazování satelitních sestavení naleznete v tématu Vytváření satelitních sestavení a balení a nasazení prostředků.

Hledání a porovnání řetězců

Kdykoli je to možné, měli byste zpracovávat řetězce jako celé řetězce místo jejich zpracování jako řady jednotlivých znaků. To je zvlášť důležité při řazení nebo hledání podřetězí, aby se zabránilo problémům spojeným s parsováním kombinovaných znaků.

Tip

Třídu můžete použít StringInfo k práci s textovými prvky, nikoli s jednotlivými znaky v řetězci.

Při hledání a porovnávání řetězců je běžnou chybou považovat řetězec za kolekci znaků, z nichž každý je reprezentován objektem Char . Ve skutečnosti může být jeden znak tvořen jedním, dvěma nebo více Char objekty. Tyto znaky se nejčastěji vyskytují v řetězcích z jazykových verzí, jejichž abecedy se skládají z znaků mimo oblast znaků základní latinky Unicode (U+0021 až U+007E). Následující příklad se pokusí najít index VELKÉ PÍSMENO LATINKY A WITH GRAVE znak (U+00C0) v řetězci. Tento znak však může být reprezentován dvěma různými způsoby: jako jedna jednotka kódu (U+00C0) nebo jako složený znak (dvě jednotky kódu: U+0041 a U+0300). V tomto případě je znak reprezentován v instanci řetězce dvěma Char objekty, U+0041 a U+0300. Příklad kódu volá String.IndexOf(Char) a String.IndexOf(String) přetížení najít pozici tohoto znaku v instanci řetězce, ale tyto vrací různé výsledky. První volání metody má Char argument; provádí pořadové porovnání, a proto nemůže najít shodu. Druhé volání má String argument; provádí porovnání citlivé na jazykovou verzi a proto najde shodu.

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:   {0}", composite.IndexOf('\u00C0'));
        Console.WriteLine("Comparing using String: {0}", 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

Můžete se vyhnout některé nejednoznačnosti tohoto příkladu (volání dvou podobných přetížení metody vracející různé výsledky) voláním přetížení, které zahrnuje StringComparison parametr, například metodu nebo String.LastIndexOf(String, StringComparison) metoduString.IndexOf(String, StringComparison).

Vyhledávání však nejsou vždy citlivá na jazykovou verzi. Pokud je účelem hledání učinit rozhodnutí o zabezpečení nebo povolit nebo zakázat přístup k určitému prostředku, mělo by být porovnání pořadové, jak je popsáno v další části.

Testovací řetězce pro rovnost

Pokud chcete otestovat dva řetězce pro rovnost, nikoli určit, jak se porovnávají v pořadí řazení, použijte String.Equals metodu místo metody porovnání řetězců, například String.Compare nebo CompareInfo.Compare.

Porovnání rovnosti se obvykle provádí pro přístup k určitému prostředku podmíněně. Můžete například provést porovnání rovnosti, abyste ověřili heslo nebo potvrdili, že soubor existuje. Taková nejazyčná porovnání by měla být vždy řadová než jazyková verze. Obecně byste měli volat metodu instance String.Equals(String, StringComparison) nebo statickou String.Equals(String, String, StringComparison) metodu s hodnotou StringComparison.Ordinal řetězců, jako jsou hesla, a hodnotu StringComparison.OrdinalIgnoreCase řetězců, jako jsou názvy souborů nebo identifikátory URI.

Porovnání rovnosti někdy zahrnují vyhledávání nebo porovnání podřetěžců místo volání String.Equals metody. V některých případech můžete pomocí hledání podřetězce určit, zda se podřetězce rovná jinému řetězci. Pokud je účelem tohoto porovnání nejazyčné, mělo by být hledání také pořadové místo jazykové verze.

Následující příklad znázorňuje nebezpečí vyhledávání citlivého na jazykovou verzi na nejazyčná data. Metoda AccessesFileSystem je navržena tak, aby zakázala přístup k systému souborů pro identifikátory URI, které začínají podřetězcem "FILE". Za tímto účelem provádí porovnání začátku identifikátoru URI s řetězcem FILE nerozlišující malá a velká písmena. Protože identifikátor URI, který přistupuje k systému souborů, může začínat na "FILE:" nebo "file:", implicitní předpoklad je, že "i" (U+0069) je vždy malými písmeny ekvivalentem "I" (U+0049). V turečtině a Ázerbájdžánštině je však velká verze "i" je "İ" (U+0130). Z důvodu této nesrovnalosti umožňuje porovnání s jazykovou verzí přístup k systému souborů, kdy by mělo být zakázáno.

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.

Tomuto problému se můžete vyhnout provedením řadového porovnání, které ignoruje případ, jak ukazuje následující příklad.

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.

Řazení a řazení řetězců

Obvykle by měly být seřazené řetězce, které se mají zobrazit v uživatelském rozhraní, seřazené na základě jazykové verze. Ve většině případů se takové porovnání řetězců zpracovává implicitně rozhraním .NET při volání metody, která seřadí řetězce, například Array.Sort nebo List<T>.Sort. Ve výchozím nastavení jsou řetězce seřazené pomocí konvencí řazení aktuální jazykové verze. Následující příklad ukazuje rozdíl, když je pole řetězců seřazeno pomocí konvencí jazykové verze angličtiny (USA) a švédské (Švédsko).

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

Porovnání řetězců citlivých na jazykovou verzi je definováno objektem CompareInfo , který je vrácen vlastností CultureInfo.CompareInfo každé jazykové verze. Porovnání řetězců citlivých na jazykovou verzi, které používají String.Compare přetížení metody, také používají CompareInfo objekt.

.NET používá tabulky k provádění řazení citlivých na jazykovou verzi u řetězcových dat. Obsah těchto tabulek, které obsahují data o hmotnostech řazení a normalizaci řetězců, je určen verzí standardu Unicode implementovaného konkrétní verzí .NET. Následující tabulka uvádí verze Unicode implementované zadanými verzemi .NET. Tento seznam podporovaných verzí Unicode se vztahuje pouze na porovnání a řazení znaků; nevztahuje se na klasifikaci znaků Unicode podle kategorie. Další informace najdete v části Řetězce a Standard unicode v String článku.

Verze rozhraní .NET Framework Operační systém Verze Unicode
.NET Framework 2,0 Všechny operační systémy Unicode 4.1
.NET Framework 3.0 Všechny operační systémy Unicode 4.1
.NET Framework 3.5 Všechny operační systémy Unicode 4.1
.NET Framework 4 Všechny operační systémy Unicode 5.0
.NET Framework 4.5 a novější Windows 7 Unicode 5.0
.NET Framework 4.5 a novější Operační systémy Windows 8 a novější Unicode 6.3.0
.NET Core a .NET 5+ Závisí na verzi standardu Unicode podporované základním operačním systémem.

Počínaje rozhraním .NET Framework 4.5 a ve všech verzích .NET Core a .NET 5+ závisí porovnání řetězců a řazení na operačním systému. Rozhraní .NET Framework 4.5 a novější ve Windows 7 načte data z vlastních tabulek, které implementují Unicode 5.0. Rozhraní .NET Framework 4.5 a novější v systému Windows 8 a novější načítá data z tabulek operačního systému, které implementují Unicode 6.3. V .NET Core a .NET 5 nebo novějších závisí podporovaná verze Unicode na základním operačním systému. Pokud serializujete seřazená data citlivá na jazykovou verzi, můžete pomocí SortVersion třídy určit, kdy je potřeba serializovaná data seřadit tak, aby byla konzistentní s .NET a pořadím řazení operačního systému. Příklad najdete v SortVersion tématu třídy.

Pokud vaše aplikace provádí rozsáhlé typy řetězcových dat specifických pro jazykovou verzi, můžete s SortKey třídou pracovat a porovnávat řetězce. Klíč řazení odráží váhy řazení specifické pro jazykovou verzi, včetně abecedy, písmen a diakritických vah konkrétního řetězce. Vzhledem k tomu, že porovnání používající klíče řazení jsou binární, jsou rychlejší než porovnání, která používají CompareInfo objekt implicitně nebo explicitně. Klíč řazení specifický pro jazykovou verzi pro konkrétní řetězec vytvoříte předáním řetězce metodě CompareInfo.GetSortKey .

Následující příklad je podobný předchozímu příkladu. Místo volání Array.Sort(Array) metody, která implicitně volá metodu CompareInfo.Compare , definuje System.Collections.Generic.IComparer<T> implementaci, která porovnává klíče řazení, které vytvoří instanci a předá metodě Array.Sort<T>(T[], IComparer<T>) .

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

Vyhněte se zřetězení řetězců

Pokud je to možné, vyhněte se použití složených řetězců vytvořených za běhu zřetězených frází. Složené řetězce jsou obtížné lokalizovat, protože často předpokládají gramatické pořadí v původním jazyce aplikace, který se nevztahuje na jiné lokalizované jazyky.

Zpracování dat a časů

Způsob zpracování hodnot data a času závisí na tom, zda jsou zobrazeny v uživatelském rozhraní nebo trvalé. V této části se podíváme na obě použití. Popisuje také, jak při práci s daty a časy zpracovávat rozdíly v časových pásmech a aritmetické operace.

Zobrazení dat a časů

Obvykle platí, že pokud se data a časy zobrazují v uživatelském rozhraní, měli byste použít konvence formátování jazykové verze uživatele, která je definována CultureInfo.CurrentCulture vlastností a DateTimeFormatInfo objektem vráceným CultureInfo.CurrentCulture.DateTimeFormat vlastností. Konvence formátování aktuální jazykové verze se automaticky použijí při formátování data pomocí některé z těchto metod:

Následující příklad zobrazuje data východů a západu slunce dvakrát pro 11. října 2012. Nejprve nastaví aktuální jazykovou verzi na chorvatštinu (Chorvatsko) a potom na angličtinu (Spojené království). V každém případě se data a časy zobrazují ve formátu, který je vhodný pro danou jazykovou verzi.

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: {0:D}", dates[0]);
        Console.WriteLine("   Sunrise: {0:T}", dates[0]);
        Console.WriteLine("   Sunset:  {0:T}", dates[1]);
    }
}

// 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

Uchování dat a časů

Data data a času byste nikdy neměli uchovávat ve formátu, který se může lišit podle jazykové verze. Jedná se o běžnou programovací chybu, která vede k poškození dat nebo výjimce za běhu. Následující příklad serializuje dvě kalendářní data: 9. ledna 2013 a 18. srpna 2013, jako řetězce pomocí konvencí formátování jazykové verze angličtiny (USA). Když se data načtou a parsují pomocí konvencí jazykové verze angličtiny (USA), úspěšně se obnoví. Pokud se však načte a parsuje pomocí konvencí anglické jazykové verze (Spojené království), první datum je nesprávně interpretováno jako 1. září a druhý se nezdaří parsovat, protože gregoriánský kalendář nemá 1. měsíc.

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: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", 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: {0}",
                          Thread.CurrentThread.CurrentCulture.DisplayName);
        foreach (var dateStr in dateStrings)
        {
            DateTime restoredDate;
            if (DateTime.TryParse(dateStr, out restoredDate))
                Console.WriteLine("The date is {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", 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

Tomuto problému se můžete vyhnout některým ze tří způsobů:

  • Serializujte datum a čas v binárním formátu, nikoli jako řetězec.
  • Uložte a parsujte řetězcovou reprezentaci data a času pomocí řetězce vlastního formátu, který je stejný bez ohledu na jazykovou verzi uživatele.
  • Uložte řetězec pomocí konvencí formátování invariantní jazykové verze.

Následující příklad znázorňuje poslední přístup. Používá konvence formátování neutrální jazykové verze vrácené statickou CultureInfo.InvariantCulture vlastností.

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: {0}",
                          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 {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", 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: {0}",
                          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 {0:D}", restoredDate);
            else
                Console.WriteLine("ERROR: Unable to parse {0}", 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

Sledování serializace a časového pásma

Hodnota data a času může mít více interpretací v rozsahu od obecného času ("Obchody otevřené 2. ledna 2013, v 9:00 do určitého okamžiku v čase ("Datum narození: 2. ledna 2013 6:32:00 M."). Pokud časová hodnota představuje určitý okamžik v čase a obnovíte ji ze serializované hodnoty, měli byste zajistit, aby představovala stejný okamžik v čase bez ohledu na zeměpisné umístění uživatele nebo časové pásmo.

Následující příklad ukazuje tento problém. Uloží jednu místní hodnotu data a času jako řetězec ve třech standardních formátech:

  • "G" pro obecný datum dlouhý čas.
  • "s" pro seřazené datum a čas.
  • "o" pro datum a čas odezvy.
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("'{0}' --> {1} {2}",
                              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

Při obnovení dat v systému ve stejném časovém pásmu jako systém, na kterém byl serializován, deserializované hodnoty data a času přesně odrážejí původní hodnotu, jak ukazuje výstup:

'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

Pokud však obnovíte data v systému v jiném časovém pásmu, zachová se informace o časovém pásmu pouze hodnota data a času, která byla formátována pomocí standardního řetězce formátu "o" (round-trip), zachová informace o časovém pásmu a proto představuje stejnou dobu v čase. Tady je výstup, kdy se data data a času obnoví v systému v romantickém standardním časovém pásmu:

'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

Pokud chcete přesně odrážet hodnotu data a času, která představuje jeden okamžik času bez ohledu na časové pásmo systému, na kterém jsou data deserializována, můžete provést některou z těchto věcí:

  • Uložte hodnotu jako řetězec pomocí standardního řetězce formátu "o" (round-trip). Potom ho deserializovat v cílovém systému.
  • Převeďte ho na UTC a uložte ho jako řetězec pomocí standardního formátovacího řetězce "r" (RFC1123). Potom ji deserializovat v cílovém systému a převést ji na místní čas.
  • Převeďte ho na UTC a uložte ho jako řetězec pomocí standardního formátovacího řetězce "u". Potom ji deserializovat v cílovém systému a převést ji na místní čas.

Následující příklad znázorňuje jednotlivé techniky.

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

Když jsou data serializována v systému v tichomořském standardním časovém pásmu a deserializována v systému v romantickém standardním časovém pásmu, příklad zobrazí následující výstup:

'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

Další informace naleznete v tématu Převod časů mezi časovými pásmy.

Provádění aritmetických aritmetických dat a času

DateTimeDateTimeOffset Aritmetické operace podporují oba typy. Můžete vypočítat rozdíl mezi dvěma hodnotami kalendářního data nebo můžete sčítat nebo odečítat konkrétní časové intervaly do nebo od hodnoty data. Aritmetické operace s hodnotami data a času však nebere v úvahu pravidla pro úpravy časových pásem a časových pásem. Z tohoto důvodu může aritmetika data a času u hodnot, které představují momenty v čase, vrátit nepřesné výsledky.

Například přechod ze standardního času Tichomoří na Tichomoří (letní čas) probíhá v druhé neděli v březnu, což je 10. března pro rok 2013. Jak ukazuje následující příklad, pokud vypočítáte datum a čas, který je 48 hodin po 9. březnu 2013 v 10:30 A.M. v systému v tichomořském standardním časovém pásmu, výsledek 11. března 2013 v 10:30 A.M., nebere v úvahu mezičasové úpravy.

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("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2);
    }
}

// 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

Pokud chcete zajistit, aby aritmetická operace v hodnotách data a času vytvářela přesné výsledky, postupujte takto:

  1. Převeďte čas ve zdrojovém časovém pásmu na UTC.
  2. Proveďte aritmetickou operaci.
  3. Pokud je výsledkem hodnota data a času, převeďte ji ze standardu UTC na čas ve zdrojovém časovém pásmu.

Následující příklad je podobný předchozímu příkladu s tím rozdílem, že tyto tři kroky správně přidají 48 hodin do 9. března 2013 v 10:30.

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("{0:g} + {1:N1} hours = {2:g}",
                          date1, interval.TotalHours, date2);
    }
}

// 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

Další informace najdete v tématu Provádění aritmetických operací s kalendářními daty a časy.

Použití názvů citlivých na jazykovou verzi pro prvky kalendářních dat

Aplikace může potřebovat zobrazit název měsíce nebo den v týdnu. K tomu je běžný kód, například následující.

using System;

public class Example12
{
   public static void Main12()
   {
      DateTime midYear = new DateTime(2013, 7, 1);
      Console.WriteLine("{0:d} is a {1}.", midYear, 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.

Tento kód ale vždy vrátí názvy dnů v týdnu v angličtině. Kód, který extrahuje název měsíce, je často ještě nepružnější. Často předpokládá dvanáctiměsíční kalendář s názvy měsíců v určitém jazyce.

Pomocí vlastních řetězců formátu data a času nebo vlastností objektu DateTimeFormatInfo je snadné extrahovat řetězce, které odrážejí názvy dnů v týdnu nebo měsících v jazykové verzi uživatele, jak je znázorněno v následujícím příkladu. Změní aktuální jazykovou verzi na francouzštinu (Francie) a zobrazí název dne v týdnu a název měsíce pro 1. července 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("{0:d} is a {1}.", midYear, DateUtilities.GetDayName(midYear));
        Console.WriteLine("{0:d} is a {1}.", midYear, DateUtilities.GetDayName((int)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));
    }
}

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.

Číselné hodnoty

Zpracování čísel závisí na tom, zda jsou zobrazeny v uživatelském rozhraní nebo trvalé. V této části se podíváme na obě použití.

Poznámka:

Při operacích analýzy a formátování rozpozná .NET jako číselné číslice pouze základní znaky latinky 0 až 9 (U+0030 až U+0039).

Zobrazení číselných hodnot

Obvykle platí, že pokud se čísla zobrazí v uživatelském rozhraní, měli byste použít konvence formátování jazykové verze uživatele, která je definována CultureInfo.CurrentCulture vlastností a NumberFormatInfo objektem vráceným CultureInfo.CurrentCulture.NumberFormat vlastností. Konvence formátování aktuální jazykové verze se automaticky použijí při formátování data následujícími způsoby:

  • Použití bezparametrové ToString metody libovolného číselného typu.
  • ToString(String) Pomocí metody libovolného číselného typu, který obsahuje formátovací řetězec jako argument.
  • Použití složeného formátování s číselnými hodnotami

Následující příklad zobrazuje průměrnou teplotu za měsíc v Paříž, Francie. Nejprve nastaví aktuální jazykovou verzi na francouzštinu (Francie) před zobrazením dat a pak ji nastaví na angličtinu (USA). V každém případě se názvy měsíců a teploty zobrazují ve formátu, který je vhodný pro danou jazykovou verzi. Všimněte si, že dvě jazykové verze používají v hodnotě teploty různé oddělovače desetinných míst. Všimněte si také, že v příkladu se k zobrazení celého názvu měsíce používá řetězec vlastního formátu data a času "MMMM" a že přiděluje odpovídající velikost místa pro název měsíce ve výsledném řetězci tak, že určí délku nejdelšího názvu měsíce v DateTimeFormatInfo.MonthNames poli.

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: {0}", 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: {0}", 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

Zachování číselných hodnot

Číselná data byste nikdy neměli uchovávat ve formátu specifickém pro jazykovou verzi. Jedná se o běžnou programovací chybu, která vede k poškození dat nebo výjimce za běhu. Následující příklad vygeneruje deset náhodných čísel s plovoucí desetinnou čárkou a potom je serializuje jako řetězce pomocí konvencí formátování jazykové verze angličtiny (USA). Když se data načtou a parsují pomocí konvencí jazykové verze angličtiny (USA), úspěšně se obnoví. Při načtení a parsování pomocí konvencí francouzské jazykové verze (Francie) se však žádná čísla nedají analyzovat, protože jazykové verze používají různé oddělovače desetinných míst.

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: {0}",
                          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 '{0}'", 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: {0}",
                          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 '{0}'", 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'

Pokud se chcete tomuto problému vyhnout, můžete použít jednu z těchto technik:

  • Uložte a parsujte řetězcovou reprezentaci čísla pomocí vlastního formátovacího řetězce, který je stejný bez ohledu na jazykovou verzi uživatele.
  • Uložte číslo jako řetězec pomocí konvencí formátování invariantní jazykové verze, která je vrácena CultureInfo.InvariantCulture vlastností.

Serializace hodnot měny je zvláštní případ. Vzhledem k tomu, že hodnota měny závisí na jednotce měny, ve které je vyjádřena, nemá smysl ji považovat za nezávislou číselnou hodnotu. Pokud však uložíte hodnotu měny jako formátovaný řetězec, který obsahuje symbol měny, nelze deserializovat v systému, jehož výchozí jazyková verze používá jiný symbol měny, jak ukazuje následující příklad.

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: {0}", CultureInfo.CurrentCulture.DisplayName);
      Console.WriteLine("Currency Value: {0:C2}", value);

      // 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 '{0}'", 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: {0}",
                        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 '{0}'", 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'

Místo toho byste měli číselnou hodnotu serializovat spolu s některými kulturními informacemi, jako je název jazykové verze, aby hodnota a její symbol měny mohly být deserializovány nezávisle na aktuální jazykové verzi. Následující příklad to provede definováním CurrencyValue struktury se dvěma členy: Decimal hodnota a název jazykové verze, do které hodnota patří.

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

Práce s nastavením specifickými pro jazykovou verzi

V .NET CultureInfo třída představuje konkrétní jazykovou verzi nebo oblast. Některé z jeho vlastností vrací objekty, které poskytují konkrétní informace o určitém aspektu jazykové verze:

Obecně platí, že nepředpokládáme žádné předpoklady o hodnotách konkrétních CultureInfo vlastností a jejich souvisejících objektech. Místo toho byste měli zobrazit data specifická pro jazykovou verzi, která se můžou změnit, a to z těchto důvodů:

  • Hodnoty jednotlivých vlastností se můžou v průběhu času měnit a revidovat, protože data jsou opravena, budou k dispozici lepší data nebo se mění konvence specifické pro jazykovou verzi.

  • Hodnoty jednotlivých vlastností se mohou lišit v různých verzích .NET nebo verzí operačního systému.

  • .NET podporuje náhradní jazykové verze. To umožňuje definovat novou vlastní jazykovou verzi, která buď doplňuje stávající standardní jazykové verze, nebo zcela nahrazuje stávající standardní jazykovou verzi.

  • V systémech Windows může uživatel přizpůsobit nastavení specifická pro jazyk pomocí aplikace Oblast a jazyk v Ovládací panely. Při vytváření instance objektu CultureInfo můžete určit, zda odráží tato uživatelská přizpůsobení voláním konstruktoru CultureInfo(String, Boolean) . U aplikací koncových uživatelů byste obvykle měli respektovat předvolby uživatelů, aby se uživateli zobrazovala data ve formátu, který očekává.

Viz také