全球化牽涉到設計及開發全球化的應用程式,可支援多種文化使用者的在地化介面和區域資料。 開始設計階段之前,您應該先判斷您的應用程式將支援的文化特性。 雖然應用程式以單一文化特性或區域作為默認目標,但您可以設計和撰寫它,以便輕鬆地擴充至其他文化特性或區域中的使用者。
身為開發人員,我們都假設使用者介面和數據是由我們的文化特性所形成。 例如,對於美國的英文開發人員來說,將日期和時間數據串行化為格式 MM/dd/yyyy hh:mm:ss
的字串似乎完全合理。 不過,在不同的文化環境中解串行化的字串可能會擲回 FormatException 例外狀況或產生不正確的數據。 全球化可讓我們識別這類文化特性特定的假設,並確保它們不會影響應用程式的設計或程序代碼。
本文討論您應該考慮的一些主要問題,以及處理全球化應用程式中字串、日期和時間值和數值時可以遵循的最佳做法。
字串
字元和字串的處理是全球化的中心焦點,因為每個文化特性或區域可能會使用不同的字元和字元集,並以不同的方式排序。 本節提供在全球化應用程式中使用字串的建議。
在內部使用 Unicode
根據預設,.NET 會使用 Unicode 字串。 Unicode 字串是由零、一或多個 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 的使用可確保相同的程式代碼單位一律對應至相同的字元,而且相同的字元一律對應至相同的位元組數位。
使用資源檔
即使您正在開發以單一文化特性或區域為目標的應用程式,您也應該使用資源檔案來儲存字串和其他顯示在使用者介面中的資源。 您不應該直接將它們新增至您的程序代碼。 使用資源檔有許多優點:
- 所有字串都位於單一位置。 您不需要在整個原始程式碼中搜尋,即可識別要針對特定語言或文化特性修改的字串。
- 不需要重複字串。 不使用資源文件的開發人員通常會在多個原始碼檔案中定義相同的字串。 此重複會增加修改字串時,將會忽略一或多個實例的機率。
- 您可以將影像或二進位數據等非字串資源包含在資源檔中,而不是將它們儲存在個別的獨立檔案中,以便輕鬆地擷取它們。
如果您要建立本地化的應用程式,則使用資源檔具有特殊優點。 當您部署資源在衛星組件中時,Common Language Runtime 會根據 CultureInfo.CurrentUICulture 屬性所定義的使用者當前 UI 文化,自動選取適當的文化資源。 只要您提供適當的文化特性特定資源,並正確地實例化 ResourceManager 物件或使用強型別資源類別,執行時就會處理擷取適當資源的細節。
如需建立資源文件的詳細資訊,請參閱 建立資源檔。 如需建立和部署附屬元件的相關信息,請參閱建立附屬元件和封裝和部署資源。
搜尋和比較字串
盡可能處理字串做為整個字串,而不是將它們當作一系列個別字元來處理。 當您排序或搜尋子字串時,這特別重要,以避免與剖析合併字元相關的問題。
小提示
您可以使用 類別 StringInfo 來處理文字元素,而不是字串中的個別字元。
在字串搜尋和比較中,常見的錯誤是將字串視為字元集合,每個字元都是由 Char 物件表示。 事實上,單一字元可能會由一、兩個或多個 Char 對象組成。 這類字元最常出現在文化特性的字串中,其字母是由 Unicode 基本拉丁字元範圍以外的字元所組成(U+0021 到 U+007E)。 下列範例會嘗試在字串中尋找拉丁大寫字母 A WITH GRAVE 字元 (U+00C0) 的索引。 不過,此字元可以用兩種不同的方式表示:單一程式代碼單位 (U+00C0) 或複合字元 (兩個代碼單位:U+0041 和 U+0300)。 在此情況下,字元會以兩 Char 個物件U+0041和U+0300在字串實例中表示。 範例程式代碼會呼叫 String.IndexOf(Char) 和 String.IndexOf(String) 多載,以尋找此字元在字串實例中的位置,但這些結果會傳回不同的結果。 第一個方法呼叫有一個 Char 參數,它會執行序數比較,因此找不到匹配項目。 第二個 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) 方法,以避免此範例的某些模棱兩可(呼叫傳回不同結果之方法的兩個類似多載)。
不過,搜尋不一定會區分文化特性。 如果搜尋的目的是要做出安全性決策,或允許或不允許存取某些資源,則比較應該為序數,如下一節所述。
測試字串是否相等
如果您想測試兩個字串是否相等,而不是判斷它們在排序中的比較結果,請使用 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 字串比較是由 CompareInfo 物件所定義的,該物件由每種文化的 屬性所傳回。 涉及文化特性敏感性的字串比較中,使用 String.Compare 方法多載時也會使用 CompareInfo 物件。
.NET 會使用數據表對字串數據執行區分文化特性的排序。 這些數據表的內容,其中包含排序權數和字串正規化的數據,是由特定 .NET 版本所實作的 Unicode 標準版本所決定。 下表列出指定之 .NET 版本所實作的 Unicode 版本。 此支援的 Unicode 版本清單僅適用於字元比較和排序;它不適用於依類別分類的 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 Standard 版本。 |
從 .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 日的日出和日落數據兩次。 它首先將目前的文化設定為克羅埃西亞(克羅埃西亞),然後設定為英文(英國)。 在每個案例中,日期和時間都會以適合該文化特性的格式顯示。
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 日這兩個日期序列化為字串。 使用英文(美國)文化特性慣例擷取和剖析數據時,已成功還原數據。 不過,使用英文(英國)文化慣例來擷取和剖析時,第一個日期錯誤地解譯為9月1日,第二個日期則無法剖析,因為公曆沒有十八個月。
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
您可以使用下列三種方式中的任何一種方式來避免此問題:
- 以二進位格式串行化日期和時間,而不是字串。
- 使用自定義格式字串來儲存及剖析日期和時間的字串表示法,不論使用者的文化特性為何,都相同。
- 使用不受文化影響的格式化慣例來儲存字串。
下列範例說明最後一種方法。 它使用靜態 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
串行化和時區感知
日期和時間值可以有多個解釋,範圍從一般時間(“商店於 2013 年 1 月 2 日開放,上午 9:00”)到特定時間時間 (“出生日期:2013 年 1 月 2 日 6:32:00 A.M.)。 當時間值代表特定時間點,並從串行化值還原時,您應該確保它代表相同的時間點,而不論使用者的地理位置或時區為何。
下列範例說明此問題。 它會以三種 標準格式將單一本機日期和時間值儲存為字串:
- “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
若要準確反映日期和時間值,不論所處系統時區為何,都能代表單一瞬間,您可以採取以下任一方式:
- 使用 「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 類型都支持算術運算。 您可以計算兩個日期值之間的差異,也可以加入或減去日期值之間的特定時間間隔。 不過,日期和時間值的算術運算不會考慮時區和時區調整規則。 因此,代表時間刻度之值的日期和時間算術可能會傳回不正確的結果。
例如,從太平洋標準時間轉換為太平洋日光時間的轉換發生在 2013 年 3 月的第二個星期日,也就是 2013 年 3 月 10 日。 如下列範例所示,如果您在太平洋標準時區的系統上計算 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轉換為來源時區中的時間。
下列範例與上一個範例類似,不同之處在於它會遵循這三個步驟,將 48 小時正確新增至 2013 年 3 月 9 日上午 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($"{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.
不過,此程式代碼一律會以英文傳回星期幾的名稱。 擷取月份名稱的程式代碼通常更不靈活。 它經常假設有一個具有特定語言月份名稱的十二個月行事曆。
藉由使用 自定義日期和時間格式字串 或 對象的屬性 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)
使用任何數值類型的方法,其中包含格式字串做為自變數。 - 使用數值時,搭配 複合格式。
下列範例會顯示法國巴黎的每月平均溫度。 它會先將目前的文化特性設定為法文(法國)再顯示數據,然後將它設定為英文(美國)。 在每個案例中,月份名稱和溫度都會以適合該文化特性的格式顯示。 請注意,這兩種文化使用不同的溫度值的小數分隔符。 另請注意,此範例會使用 「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
保存數值
您絕對不應該以特定文化特性格式保存數值數據。 這是常見的程式設計錯誤,導致數據損毀或運行時間例外狀況。 下列範例會產生十個隨機浮點數,然後使用英文(美國)文化特性的格式慣例,將其串行化為字串。 使用英文(美國)文化特性慣例擷取和剖析數據時,已成功還原數據。 不過,當使用法國文化的慣例擷取和剖析時,因為文化使用不同的小數分隔符,所有的數字都無法被剖析。
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
結構來做到這一點: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) 建構函式來判斷它是否反映這些使用者自定義專案。 一般而言,對於使用者應用程式,您應該遵守使用者喜好設定,讓使用者以預期格式呈現數據。