グローバリゼーションには、複数のカルチャのユーザー向けにローカライズされたインターフェイスと地域データをサポートする、世界対応のアプリの設計と開発が含まれます。 設計フェーズを開始する前に、アプリがサポートするカルチャを決定する必要があります。 アプリは 1 つのカルチャまたはリージョンを既定として対象としますが、他のカルチャや地域のユーザーに簡単に拡張できるように、アプリを設計および記述できます。
開発者は皆、カルチャによって形成されるユーザー インターフェイスとデータに関する前提を持っています。 たとえば、米国の英語を話す開発者にとって、日付と時刻のデータを文字列としてシリアル化する MM/dd/yyyy hh:mm:ss
は、完全に妥当なようです。 ただし、異なるカルチャのシステムでその文字列を逆シリアル化すると、FormatException 例外が発生したり、不正確なデータが生成されたりする可能性があります。 グローバリゼーションを使用すると、このようなカルチャ固有の前提を特定し、アプリの設計やコードに影響を与えないことを確認できます。
この記事では、考慮すべき主な問題の一部と、グローバル化されたアプリで文字列、日付と時刻の値、数値を処理するときに従うことができるベスト プラクティスについて説明します。
ストリングス
文字と文字列の処理は、各カルチャまたは領域で異なる文字と文字セットを使用し、異なる方法で並べ替える可能性があるため、グローバリゼーションの中心的な焦点です。 このセクションでは、グローバル化されたアプリで文字列を使用するための推奨事項を示します。
Unicode を内部的に使用する
既定では、.NET では Unicode 文字列が使用されます。 Unicode 文字列は、0 個、1 つ以上の Char オブジェクトで構成され、それぞれが UTF-16 コード単位を表します。 世界中で使用されているすべての文字セットのほぼすべての文字に Unicode 表現があります。
Windows オペレーティング システムを含む多くのアプリケーションとオペレーティング システムでは、コード ページを使用して文字セットを表すこともできます。 コード ページには通常、0x00から 0x7F までの標準 ASCII 値が含まれており、他の文字を 0x80 から 0xFF までの残りの値にマップします。 0x80から0xFFまでの値の解釈は、特定のコード ページによって異なります。 このため、可能であれば、グローバル化されたアプリでコード ページを使用しないようにする必要があります。
次の例は、システム上の既定のコード ページが、データが保存されたコード ページと異なる場合に、コード ページ データを解釈する危険性を示しています。 (このシナリオをシミュレートするために、この例では異なるコード ページを明示的に指定します)。最初に、ギリシャ語のアルファベットの大文字で構成される配列を定義します。 コードページ 737 (MS-DOS ギリシャ) を使用してバイト配列にエンコードし、そのバイト配列をファイルに保存します。 ファイルが取得され、そのバイト配列がコード・ページ 737 を使用してデコードされると、元の文字が復元されます。 ただし、ファイルが取得され、そのバイト配列がコード ページ 1252 (またはラテンアルファベットの文字を表す Windows-1252) を使用してデコードされた場合、元の文字は失われます。
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:
' ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
' €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—
Unicode を使用すると、同じコード単位が常に同じ文字にマップされ、同じ文字が常に同じバイト配列にマップされるようになります。
リソース ファイルを使用する
単一のカルチャまたはリージョンを対象とするアプリを開発している場合でも、リソース ファイルを使用して、ユーザー インターフェイスに表示される文字列やその他のリソースを格納する必要があります。 コードに直接追加しないでください。 リソース ファイルの使用には、いくつかの利点があります。
- すべての文字列は 1 つの場所にあります。 特定の言語またはカルチャを変更する文字列を識別するために、ソース コード全体を検索する必要はありません。
- 文字列を複製する必要はありません。 リソース ファイルを使用しない開発者は、多くの場合、複数のソース コード ファイルで同じ文字列を定義します。 この重複により、文字列が変更されたときに 1 つ以上のインスタンスが見落とされる可能性が高くなります。
- イメージやバイナリ データなどの文字列以外のリソースは、別のスタンドアロン ファイルに格納するのではなく、リソース ファイルに含めることができるため、簡単に取得できます。
ローカライズされたアプリを作成する場合、リソース ファイルの使用には特に利点があります。 サテライト アセンブリにリソースをデプロイすると、共通言語ランタイムは、 CultureInfo.CurrentUICulture プロパティで定義されているユーザーの現在の UI カルチャに基づいて、カルチャに適したリソースを自動的に選択します。 適切なカルチャ固有のリソースを指定し、 ResourceManager オブジェクトを正しくインスタンス化するか、厳密に型指定されたリソース クラスを使用する限り、ランタイムは適切なリソースの取得の詳細を処理します。
リソース・ファイルの作成の詳細については、リソース・ファイル の作成を参照してください。 サテライト アセンブリの作成と配置の詳細については、「サテライト アセンブリの 作成 」および「 リソースのパッケージ化とデプロイ」を参照してください。
文字列の検索と比較
可能な限り、文字列を一連の個々の文字として処理するのではなく、文字列全体として処理する必要があります。 これは、結合された文字の解析に関連する問題を防ぐために、部分文字列を並べ替えたり検索したりする場合に特に重要です。
ヒント
StringInfo クラスを使用して、文字列内の個々の文字ではなくテキスト要素を操作できます。
文字列の検索と比較では、一般的な間違いは、文字列を文字のコレクションとして扱うことであり、それぞれが Char オブジェクトによって表されます。 実際には、1 つの文字は、1 つ、2 つ、または複数の Char オブジェクトによって形成される場合があります。 このような文字は、Unicode Basic ラテン文字範囲 (U+0021 から U+007E) の範囲外の文字で構成されるアルファベットを持つカルチャの文字列で最も頻繁に見られます。 次の例では、文字列内のラテン大文字 A WITH GRAVE 文字 (U+00C0) のインデックスを検索します。 ただし、この文字は、1 つのコード単位 (U+00C0) または複合文字 (U+0041 と U+0300 の 2 つのコード単位) の 2 つの異なる方法で表すことができます。 この場合、文字列インスタンスでは、U+0041 と U+0300 の 2 つの Char オブジェクトによって文字が表されます。 このコード例では、 String.IndexOf(Char) と String.IndexOf(String) オーバーロードを呼び出して、文字列インスタンス内のこの文字の位置を検索しますが、これらは異なる結果を返します。 最初のメソッド呼び出しには Char 引数があります。序数比較を実行するため、一致するものが見つかりません。 2 番目の呼び出しには String 引数があります。カルチャに依存する比較を実行するため、一致が見つかります。
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
StringComparisonやString.IndexOf(String, StringComparison) メソッドなどのString.LastIndexOf(String, StringComparison) パラメーターを含むオーバーロードを呼び出すことで、この例のあいまいさ (異なる結果を返すメソッドの 2 つの同様のオーバーロードの呼び出し) を回避できます。
ただし、検索は必ずしも文化に敏感ではありません。 検索の目的がセキュリティ上の決定を行う場合、またはリソースへのアクセスを許可または禁止する場合は、次のセクションで説明するように、比較を序数にする必要があります。
文字列の等価性をテストする
並べ替え順序で比較する方法を決定するのではなく、2 つの文字列の等価性をテストする場合は、String.EqualsやString.Compareなどの文字列比較メソッドではなく、CompareInfo.Compare メソッドを使用します。
等価性の比較は、通常、条件付きで一部のリソースにアクセスするために実行されます。 たとえば、パスワードを検証したり、ファイルが存在することを確認したりするために、等価性の比較を実行できます。 このような非言語的な比較は、常にカルチャに依存するのではなく序数である必要があります。 一般に、インスタンス String.Equals(String, StringComparison) メソッドまたは静的 String.Equals(String, String, StringComparison) メソッドを呼び出し、パスワードなどの文字列の値を StringComparison.Ordinal し、ファイル名や URI などの文字列の値を StringComparison.OrdinalIgnoreCase する必要があります。
等価性の比較には、 String.Equals メソッドの呼び出しではなく、検索や部分文字列の比較が含まれる場合があります。 場合によっては、部分文字列検索を使用して、その部分文字列が別の文字列と等しいかどうかを判断できます。 この比較の目的が言語的でない場合は、カルチャに依存するのではなく、検索の順序も指定する必要があります。
次の例は、非言語データに対するカルチャに依存する検索の危険性を示しています。
AccessesFileSystem
メソッドは、部分文字列 "FILE" で始まる URI のファイル システム アクセスを禁止するように設計されています。 これを実行するため、カルチャに依存し、大文字と小文字を区別しない比較を実行して、URI の先頭と文字列 "FILE" を比較します。 ファイル システムにアクセスする URI は "FILE:" または "file:" で始めることができるため、暗黙的な前提は、"i" (U+0069) は常に小文字の "I" (U+0049) に相当します。 ただし、トルコ語とアゼルバイジャン語では、大文字の "i" は "İ" (U+0130) です。 この不一致のため、カルチャに依存する比較では、禁止する必要がある場合にファイル システムへのアクセスが許可されます。
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.
次の例に示すように、大文字と小文字を無視する序数に基づく比較を実行して、この問題を回避できます。
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.
文字列の整列と並べ替え
通常、ユーザー インターフェイスに表示される順序付けられた文字列は、カルチャに基づいて並べ替える必要があります。 ほとんどの場合、このような文字列比較は、 Array.Sort や List<T>.Sortなどの文字列を並べ替えるメソッドを呼び出すときに、.NET によって暗黙的に処理されます。 デフォルトでは、文字列は現在の文化のソート規則を使用して並べ替えられます。 次の例は、英語 (米国) カルチャとスウェーデン語 (スウェーデン) カルチャの規則を使用して文字列の配列を並べ替える場合の違いを示しています。
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
カルチャに依存する文字列比較は、各カルチャのCompareInfo プロパティによって返されるCultureInfo.CompareInfo オブジェクトによって定義されます。 String.Compare メソッドのオーバーロードを使用するカルチャに依存する文字列比較でも、CompareInfo オブジェクトが使用されます。
.NET では、テーブルを使用して、文字列データに対してカルチャに依存する並べ替えを実行します。 並べ替えの重みと文字列の正規化に関するデータを含むこれらのテーブルの内容は、.NET の特定のバージョンによって実装される Unicode 標準のバージョンによって決まります。 次の表に、指定したバージョンの .NET によって実装される Unicode のバージョンを示します。 サポートされている Unicode バージョンの一覧は、文字の比較と並べ替えにのみ適用されます。カテゴリ別の Unicode 文字の分類には適用されません。 詳細については、 String 記事の「文字列と Unicode 標準」セクションを参照してください。
.NET Framework のバージョン | オペレーティング システム | Unicode バージョン |
---|---|---|
.NET Framework 2.0 | すべてのオペレーティング システム | Unicode 4.1 |
.NET Framework 3.0 | すべてのオペレーティング システム | Unicode 4.1 |
.NET Framework 3.5 | すべてのオペレーティング システム | Unicode 4.1 |
.NET Framework 4 | すべてのオペレーティング システム | Unicode 5.0 |
.NET Framework 4.5 以降 | Windows 7 | Unicode 5.0 |
.NET Framework 4.5 以降 | Windows 8 以降のオペレーティング システム | Unicode 6.3.0 |
.NET Core と .NET 5 以降 | 基になる OS でサポートされている Unicode 標準のバージョンによって異なります。 |
.NET Framework 4.5 以降、すべてのバージョンの .NET Core および .NET 5 以降では、文字列の比較と並べ替えはオペレーティング システムによって異なります。 Windows 7 で実行されている .NET Framework 4.5 以降では、Unicode 5.0 を実装する独自のテーブルからデータが取得されます。 Windows 8 以降で実行されている .NET Framework 4.5 以降では、Unicode 6.3 を実装するオペレーティング システム テーブルからデータが取得されます。 .NET Core および .NET 5 以降では、サポートされている Unicode のバージョンは、基になるオペレーティング システムによって異なります。 カルチャに依存する並べ替えられたデータをシリアル化する場合は、 SortVersion クラスを使用して、シリアル化されたデータを .NET とオペレーティング システムの並べ替え順序と一致するように並べ替える必要があるタイミングを判断できます。 例については、 SortVersion クラスのトピックを参照してください。
アプリで広範なカルチャ固有の種類の文字列データを実行する場合は、 SortKey クラスを使用して文字列を比較できます。 並べ替えキーは、特定の文字列のアルファベット順、大文字と小文字の区別、発音の区別など、カルチャ固有の並べ替えウェイトを反映しています。 並べ替えキーを使用する比較はバイナリであるため、暗黙的または明示的に CompareInfo オブジェクトを使用する比較よりも高速です。 特定の文字列のカルチャ固有の並べ替えキーを作成するには、文字列を CompareInfo.GetSortKey メソッドに渡します。
次の例は、前の例のようになります。 ただし、Array.Sort(Array) メソッドを暗黙的に呼び出すCompareInfo.Compare メソッドを呼び出す代わりに、並べ替えキーを比較するSystem.Collections.Generic.IComparer<T>実装を定義します。この実装は、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
文字列連結を回避する
可能であれば、連結されたフレーズから実行時に構築される複合文字列を使用しないでください。 複合文字列は、多くの場合、他のローカライズされた言語には適用されないアプリの元の言語で文法上の順序を想定するため、ローカライズが困難です。
日付と時刻を処理する
日付と時刻の値の処理方法は、ユーザー インターフェイスに表示されるか、永続化されているかによって異なります。 このセクションでは、両方の使用法について説明します。 また、日付と時刻を操作するときにタイム ゾーンの違いと算術演算を処理する方法についても説明します。
日付と時刻を表示する
通常、ユーザー インターフェイスに日付と時刻が表示される場合は、ユーザーのカルチャの書式設定規則を使用する必要があります。これは、CultureInfo.CurrentCulture プロパティと、DateTimeFormatInfo プロパティによって返されるCultureInfo.CurrentCulture.DateTimeFormat
オブジェクトによって定義されます。 現在のカルチャの書式設定規則は、次のいずれかのメソッドを使用して日付を書式設定するときに自動的に使用されます。
- パラメーターなしの DateTime.ToString() メソッド。
- 書式指定文字列を含む DateTime.ToString(String) メソッド。
- パラメーターなしの DateTimeOffset.ToString() メソッド。
- DateTimeOffset.ToString(String)は書式指定文字列を含んでいます。
- 日付と共に使用される 複合書式設定 機能。
次の例では、2012 年 10 月 11 日の日の出と日の入りのデータを 2 回表示します。 最初に、現在のカルチャをクロアチア語 (クロアチア) に、次に英語 (英国) に設定します。 いずれの場合も、日付と時刻は、そのカルチャに適した形式で表示されます。
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
日付と時刻を保持する
日付と時刻のデータは、カルチャによって異なる形式で保持しないでください。 これは、データの破損または実行時の例外を発生する一般的なプログラミング エラーです。 次の例では、英語 (米国) カルチャの書式設定規則を使用して、2013 年 1 月 9 日と 2013 年 8 月 18 日の 2 つの日付を文字列としてシリアル化します。 英語 (米国) カルチャの規則を使用してデータが取得および解析されると、正常に復元されます。 ただし、英語 (英国) カルチャの規則を使用して取得および解析されると、最初の日付は 9 月 1 日と誤って解釈され、グレゴリオ暦には 18 月がないため、2 番目の日付は解析に失敗します。
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
この問題は、次の 3 つの方法のいずれかで回避できます。
- 日付と時刻を文字列ではなくバイナリ形式でシリアル化します。
- ユーザーのカルチャに関係なく同じカスタム書式指定文字列を使用して、日付と時刻の文字列形式を保存して解析します。
- インバリアント カルチャの書式設定規則を使用して、文字列を保存します。
次の例は、最後のアプローチを示しています。 静的な 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
シリアル化とタイム ゾーンの認識
日付と時刻の値には、一般的な時刻 ("The stores open on January 2, 2013, at 9:00 A.M.") から特定の時点 ("Date of birth: January 2, 2013 6:32:00 A.M.") まで、複数の解釈を使用できます。 時刻値が特定の時点を表し、シリアル化された値から復元する場合は、ユーザーの地理的な場所やタイム ゾーンに関係なく、同じ時点を表すようにする必要があります。
次の例は、この問題を示しています。 1 つのローカルの日付と時刻の値を 3 つの 標準形式の文字列として保存します。
- 一般の日付と長い形式の時刻の場合は "G"。
- 並べ替え可能な日付/時刻の場合は "s" です。
- ラウンドトリップの日付/時刻の場合は "o" です。
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
シリアル化されたシステムと同じタイム ゾーンのシステムでデータが復元されると、逆シリアル化された日付と時刻の値は、出力に示すように元の値を正確に反映します。
'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
ただし、異なるタイム ゾーンのシステム上のデータを復元する場合は、"o" (ラウンドトリップ) 標準書式指定文字列で書式設定された日付と時刻の値のみがタイム ゾーン情報を保持するため、同じインスタント イン タイムを表します。 ロマンス標準タイム ゾーンのシステムで日付と時刻のデータが復元される場合の出力を次に示します。
'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
データが逆シリアル化されるシステムのタイム ゾーンに関係なく、1 つの時刻を表す日付と時刻の値を正確に反映するには、次のいずれかの操作を行います。
- "o" (ラウンドトリップ) 標準書式指定文字列を使用して、値を文字列として保存します。 次に、ターゲット システムで逆シリアル化します。
- それを UTC に変換し、"r" (RFC1123) 標準書式指定文字列を使用して文字列として保存します。 次に、ターゲット システムで逆シリアル化し、ローカル時刻に変換します。
- それを UTC に変換し、"u" (汎用並べ替え可能) 標準書式指定文字列を使用して文字列として保存します。 次に、ターゲット システムで逆シリアル化し、ローカル時刻に変換します。
次の例は、各手法を示しています。
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
データが太平洋標準タイム ゾーンのシステムでシリアル化され、ロマンス標準タイム ゾーンのシステムで逆シリアル化されると、次の出力が表示されます。
'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
詳細については、「 タイム ゾーン間の時刻の変換」を参照してください。
日付と時刻の算術演算を実行する
DateTime型とDateTimeOffset型の両方で算術演算がサポートされます。 2 つの日付値の差を計算することも、日付値との間で特定の時間間隔を加算または減算することもできます。 ただし、日付と時刻の値に対する算術演算では、タイム ゾーンとタイム ゾーン調整ルールは考慮されません。 このため、時刻の瞬間を表す値に対する日付と時刻の算術演算では、不正確な結果が返される可能性があります。
たとえば、太平洋標準時から太平洋夏時間への移行は、2013 年の 3 月 10 日である 3 月の第 2 日曜日に行われます。 次の例に示すように、太平洋標準時ゾーンのシステムで 2013 年 3 月 9 日午前 10 時 30 分から 48 時間後の日付と時刻を計算した場合、2013 年 3 月 11 日午前 10 時 30 分には、介入する時間調整は考慮されません。
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
日付と時刻の値に対する算術演算によって正確な結果が得られるようにするには、次の手順に従います。
- ソース タイム ゾーンの時刻を UTC に変換します。
- 算術演算を実行します。
- 結果が日付と時刻の値である場合は、UTC からソース タイム ゾーンの時刻に変換します。
次の例は前の例と似ていますが、2013 年 3 月 9 日午前 10 時 30 分に 48 時間を正しく追加するには、次の 3 つの手順に従います。
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
詳細については、「 日付と時刻を使用した算術演算の実行」を参照してください。
日付要素にカルチャに依存する名前を使用する
アプリでは、月の名前または曜日の表示が必要になる場合があります。 これを行うには、次のようなコードが一般的です。
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.
ただし、このコードは常に曜日の名前を英語で返します。 月の名前を抽出するコードは、多くの場合、さらに柔軟性が高くありません。 多くの場合、特定の言語の月の名前を持つ 12 か月のカレンダーを想定しています。
次の例に示すように 、カスタムの日時書式指定文字列 または DateTimeFormatInfo オブジェクトのプロパティを使用すると、ユーザーのカルチャで曜日または月の名前を反映する文字列を簡単に抽出できます。 この例では、現在のカルチャをフランス語 (フランス) に変更し、2013 年 7 月 1 日の曜日の名前と月の名前を表示します。
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.
数値
数値の処理は、ユーザー インターフェイスに表示されるか、永続化されているかによって異なります。 このセクションでは、両方の使用法について説明します。
注
解析および書式設定操作では、.NET では、基本ラテン文字 0 から 9 (U+0030 から U+0039) のみが数字として認識されます。
数値を表示する
通常、ユーザー インターフェイスに数値が表示される場合は、ユーザーのカルチャの書式設定規則を使用する必要があります。これは、CultureInfo.CurrentCulture プロパティと、NumberFormatInfo プロパティによって返されるCultureInfo.CurrentCulture.NumberFormat
オブジェクトによって定義されます。 現在のカルチャの書式設定規則は、次の方法で日付を書式設定するときに自動的に使用されます。
- 任意の数値型のパラメーターなしの
ToString
メソッドを使用します。 - 引数として書式指定文字列を含む任意の数値型の
ToString(String)
メソッドを使用します。 - 数値 を使用した複合書式 の使用。
次の使用例は、パリ, フランス の 1 ヶ月あたりの平均温度を表示します。 データを表示する前に、最初に現在のカルチャをフランス語 (フランス) に設定し、次に英語 (米国) に設定します。 いずれの場合も、月の名前と温度は、そのカルチャに適した形式で表示されます。 2 つのカルチャでは、温度値に異なる小数点区切り記号が使用されることに注意してください。 また、この例では、"MMMM" カスタムの日付と時刻の書式指定文字列を使用して完全な月の名前を表示し、 DateTimeFormatInfo.MonthNames 配列内の最も長い月の名前の長さを決定することによって、結果文字列の月名に適切な量の領域を割り当てることにも注意してください。
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
数値を保持する
カルチャ固有の形式で数値データを保持しないでください。 これは、データの破損または実行時の例外を発生する一般的なプログラミング エラーです。 次の例では、10 個のランダムな浮動小数点数を生成し、英語 (米国) カルチャの書式設定規則を使用して文字列としてシリアル化します。 英語 (米国) カルチャの規則を使用してデータが取得および解析されると、正常に復元されます。 ただし、フランス語 (フランス) カルチャの規則を使用して取得および解析する場合、カルチャで異なる小数点区切り記号が使用されるため、どの数値も解析できません。
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'
この問題を回避するには、次のいずれかの手法を使用します。
- ユーザーのカルチャに関係なく同じカスタム書式指定文字列を使用して、数値の文字列形式を保存して解析します。
- CultureInfo.InvariantCulture プロパティによって返されるインバリアント カルチャの書式設定規則を使用して、数値を文字列として保存します。
通貨値のシリアル化は特殊なケースです。 通貨値は表現される通貨の単位に依存するため、独立した数値として扱うのはほとんど意味がありません。 ただし、通貨記号を含む書式設定された文字列として通貨値を保存する場合、次の例に示すように、既定のカルチャで別の通貨記号が使用されているシステムでは逆シリアル化できません。
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'
代わりに、現在のカルチャとは別に値とその通貨記号を逆シリアル化できるように、カルチャの名前などのカルチャ情報と共に数値をシリアル化する必要があります。 次の例では、CurrencyValue
値と値が属するカルチャの名前という 2 つのメンバーを持つDecimal構造体を定義します。
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
カルチャ固有の設定を使用する
.NET では、 CultureInfo クラスは特定のカルチャまたはリージョンを表します。 そのプロパティの一部は、カルチャの一部の側面に関する特定の情報を提供するオブジェクトを返します。
CultureInfo.CompareInfo プロパティは、カルチャが文字列を比較および順序付けする方法に関する情報を含むCompareInfo オブジェクトを返します。
CultureInfo.DateTimeFormat プロパティは、日付と時刻のデータの書式設定に使用されるカルチャ固有の情報を提供するDateTimeFormatInfo オブジェクトを返します。
CultureInfo.NumberFormat プロパティは、数値データの書式設定に使用されるカルチャ固有の情報を提供するNumberFormatInfo オブジェクトを返します。
CultureInfo.TextInfo プロパティは、カルチャの文字システムに関する情報を提供するTextInfo オブジェクトを返します。
一般に、特定の CultureInfo プロパティとその関連オブジェクトの値に関する想定は一切行わないでください。 代わりに、次の理由により、カルチャ固有のデータを変更の対象として表示する必要があります。
個々のプロパティ値は、データの修正、データの改善、またはカルチャ固有の規則の変更に伴い、時間の経過とともに変更されることがあります。
個々のプロパティ値は、.NET またはオペレーティング システムのバージョンによって異なる場合があります。
.NET では、置換カルチャがサポートされています。 これにより、既存の標準カルチャを補完するか、既存の標準カルチャを完全に置き換える新しいカスタム カルチャを定義できます。
Windows システムでは、ユーザーはコントロール パネルの [地域と言語 ] アプリを使用して、カルチャ固有の設定をカスタマイズできます。 CultureInfo オブジェクトをインスタンス化するときに、CultureInfo(String, Boolean) コンストラクターを呼び出すことによって、これらのユーザーのカスタマイズが反映されているかどうかを判断できます。 通常、エンド ユーザー アプリの場合は、ユーザーが期待する形式でデータが表示されるように、ユーザー設定を尊重する必要があります。
こちらも参照ください
.NET