세계화에는 여러 문화권의 사용자를 위해 지역화된 인터페이스 및 지역 데이터를 지원하는 세계화가 가능한 앱을 디자인하고 개발하는 작업이 포함됩니다. 디자인 단계를 시작하기 전에 앱에서 지원할 문화권을 결정해야 합니다. 앱은 단일 문화권 또는 지역을 기본값으로 대상으로 하지만 다른 문화권 또는 지역의 사용자로 쉽게 확장할 수 있도록 디자인하고 작성할 수 있습니다.
개발자로서 우리 모두는 문화권에 의해 형성된 사용자 인터페이스 및 데이터에 대한 가정을 가지고 있습니다. 예를 들어 미국에서 영어를 구사하는 개발자의 경우 날짜 및 시간 데이터를 형식 MM/dd/yyyy hh:mm:ss
의 문자열로 직렬화하는 것이 매우 합리적입니다. 그러나 다른 문화권의 시스템에서 해당 문자열을 역직렬화하면 예외가 FormatException 발생하거나 부정확한 데이터가 생성됩니다. 세계화를 사용하면 이러한 문화권별 가정을 식별하고 앱의 디자인이나 코드에 영향을 미치지 않도록 할 수 있습니다.
이 문서에서는 고려해야 할 몇 가지 주요 문제와 전역화된 앱에서 문자열, 날짜 및 시간 값 및 숫자 값을 처리할 때 수행할 수 있는 모범 사례를 설명합니다.
현악기들
각 문화권 또는 지역에서 서로 다른 문자와 문자 집합을 사용하고 다르게 정렬할 수 있으므로 문자 및 문자열 처리는 세계화의 중심입니다. 이 섹션에서는 세계화된 앱에서 문자열을 사용하기 위한 권장 사항을 제공합니다.
내부적으로 유니코드 사용
기본적으로 .NET은 유니코드 문자열을 사용합니다. 유니코드 문자열은 각각 UTF-16 코드 단위를 나타내는 0개, 하나 이상의 Char 개체로 구성됩니다. 전 세계에서 사용 중인 모든 문자 집합의 거의 모든 문자에 대한 유니코드 표현이 있습니다.
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:
' ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ
' €‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’""•–—
유니코드를 사용하면 동일한 코드 단위가 항상 동일한 문자에 매핑되고 동일한 문자가 항상 동일한 바이트 배열에 매핑됩니다.
리소스 파일 사용
단일 문화권 또는 지역을 대상으로 하는 앱을 개발하는 경우에도 리소스 파일을 사용하여 사용자 인터페이스에 표시되는 문자열 및 기타 리소스를 저장해야 합니다. 코드에 직접 추가해서는 안 됩니다. 리소스 파일을 사용하는 경우 다음과 같은 여러 가지 이점이 있습니다.
- 모든 문자열은 단일 위치에 있습니다. 특정 언어 또는 문화권에 대해 수정할 문자열을 식별하기 위해 소스 코드 전체에서 검색할 필요가 없습니다.
- 문자열을 복제할 필요가 없습니다. 리소스 파일을 사용하지 않는 개발자는 종종 여러 소스 코드 파일에서 동일한 문자열을 정의합니다. 이 중복은 문자열이 수정될 때 하나 이상의 인스턴스가 간과될 확률을 높입니다.
- 이미지 또는 이진 데이터와 같은 문자열이 아닌 리소스를 별도의 독립 실행형 파일에 저장하는 대신 리소스 파일에 포함할 수 있으므로 쉽게 검색할 수 있습니다.
리소스 파일을 사용하는 경우 지역화된 앱을 만드는 경우 특히 이점이 있습니다. 위성 어셈블리에 리소스를 배포할 때, 공용 언어 런타임은 사용자의 현재 UI 문화권을 CultureInfo.CurrentUICulture 속성에 따라 정의하고, 문화를 적절히 반영하는 리소스를 자동으로 선택합니다. 적절한 문화권별 리소스를 제공하고 개체를 올바르게 인스턴스화 ResourceManager 하거나 강력한 형식의 리소스 클래스를 사용하는 한 런타임은 적절한 리소스 검색의 세부 정보를 처리합니다.
리소스 파일을 만드는 방법에 대한 자세한 내용은 리소스 파일 만들기를 참조하세요. 위성 어셈블리를 만들고 배포하는 방법에 대한 자세한 내용은 위성 어셈블리 만들기 및 리소스 패키지 및 배포를 참조하세요.
문자열 검색 및 비교
가능하면 문자열을 일련의 개별 문자로 처리하는 대신 전체 문자열로 처리해야 합니다. 이는 결합된 문자 구문 분석과 관련된 문제를 방지하기 위해 부분 문자열을 정렬하거나 검색할 때 특히 중요합니다.
팁 (조언)
클래스를 사용하여 문자열의 StringInfo 개별 문자가 아닌 텍스트 요소를 사용할 수 있습니다.
문자열 검색 및 비교에서 일반적인 실수는 문자열을 각 개체가 나타내는 Char 문자 컬렉션으로 처리하는 것입니다. 실제로 단일 문자는 하나, 둘 이상의 Char 개체로 형성될 수 있습니다. 이러한 문자는 알파벳이 유니코드 기본 라틴 문자 범위(U+0021~ U+007E) 외부의 문자로 구성된 문화권의 문자열에서 가장 자주 발견됩니다. 다음 예제에서는 문자열에서 라틴 대문자 A 위에 물음표가 있는 문자(U+00C0)의 인덱스를 찾습니다. 그러나 이 문자는 단일 코드 단위(U+00C0) 또는 복합 문자(U+0041 및 U+0300의 두 코드 단위)로 두 가지 방법으로 나타낼 수 있습니다. 이 경우 문자는 문자열 인스턴스에서 U+0041 및 U+0300이라는 두 개체 Char 로 표시됩니다. 예제 코드는 문자열 인스턴스에서 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) 메서드를 호출하고, 파일 이름 또는 URI와 같은 문자열에 대해 StringComparison.Ordinal 값을 사용하여 정적 StringComparison.OrdinalIgnoreCase 메서드를 호출해야 합니다.
같음 비교에는 메서드에 대한 호출이 아닌 검색 또는 부분 문자열 비교가 String.Equals 포함되는 경우가 있습니다. 경우에 따라 부분 문자열 검색을 사용하여 해당 부분 문자열이 다른 문자열과 같은지 여부를 확인할 수 있습니다. 이 비교의 목적이 비언어적이라면, 검색은 문화에 민감한 것보다 서수이어야 합니다.
다음 예제에서는 비언어적 데이터에 대한 문화권 구분 검색의 위험을 보여 줍니다. 이 AccessesFileSystem
메서드는 부분 문자열 "FILE"로 시작하는 URI에 대한 파일 시스템 액세스를 금지하도록 설계되었습니다. URI의 시작 부분을 문자열 "FILE"과 문화권에 민감하되 대/소문자 구분 없이 비교하는 작업을 수행합니다. 파일 시스템에 액세스하는 URI는 "FILE:" 또는 "file:"로 시작할 수 있으므로 암시적 가정은 "i"(U+0069)가 항상 "I"(U+0049)와 같은 소문자라는 것입니다. 그러나 터키어와 아제르바이잔어에서는 "i"의 대문자 버전이 "1"(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.SortList<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에서 구현된 유니코드 표준 버전에 따라 결정됩니다. 다음 표에서는 지정된 버전의 .NET에서 구현한 유니코드 버전을 나열합니다. 지원되는 유니코드 버전 목록은 문자 비교 및 정렬에만 적용됩니다. 범주별 유니코드 문자 분류에는 적용되지 않습니다. 자세한 내용은 문서의 "문자열 및 유니코드 표준" 섹션을 String 참조하세요.
.NET Framework 버전 | 운영 체제 | 유니코드 버전 |
---|---|---|
.NET Framework 2.0 | 모든 운영 체제 | 유니코드 4.1 |
.NET Framework 3.0 | 모든 운영 체제 | 유니코드 4.1 |
.NET Framework 3.5 | 모든 운영 체제 | 유니코드 4.1 |
.NET Framework 4 | 모든 운영 체제 | 유니코드 5.0 |
.NET Framework 4.5 이상 | Windows 7 | 유니코드 5.0 |
.NET Framework 4.5 이상 | Windows 8 이상 운영 체제 | 유니코드 6.3.0 |
.NET Core 및 .NET 5 이상 | 기본 OS에서 지원하는 유니코드 표준의 버전에 따라 달라집니다. |
.NET Framework 4.5부터 모든 버전의 .NET Core 및 .NET 5 이상에서 문자열 비교 및 정렬은 운영 체제에 따라 달라집니다. Windows 7에서 실행되는 .NET Framework 4.5 이상은 유니코드 5.0을 구현하는 자체 테이블에서 데이터를 검색합니다. Windows 8 이상에서 실행되는 .NET Framework 4.5 이상은 유니코드 6.3을 구현하는 운영 체제 테이블에서 데이터를 검색합니다. .NET Core 및 .NET 5 이상에서 지원되는 유니코드 버전은 기본 운영 체제에 따라 달라집니다. 문화권에 민감한 정렬된 데이터를 직렬화하는 경우 클래스를 사용하여 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일로 잘못 해석되고 두 번째 날짜는 그레고리오력에 18번째 달이 없기 때문에 구문 분석에 실패합니다.
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")에 이르기까지 여러 해석을 가질 수 있습니다. 시간 값이 특정 시간을 나타내고 직렬화된 값에서 복원하는 경우 사용자의 지리적 위치 또는 표준 시간대에 관계없이 동일한 시간을 나타내는지 확인해야 합니다.
다음 예제에서는 이 문제를 보여 줍니다. 단일 로컬 날짜 및 시간 값을 세 가지 표준 형식의 문자열로 저장합니다.
- "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"(왕복) 표준 형식 문자열을 사용하여 값을 문자열로 저장합니다. 그런 다음 대상 시스템에서 역직렬화합니다.
- "r"(RFC1123) 표준 형식 문자열을 사용하여 UTC로 변환하고 문자열로 저장합니다. 그런 다음 대상 시스템에서 역직렬화하고 현지 시간으로 변환합니다.
- 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
데이터가 Pacific Standard 표준 시간대의 시스템에서 직렬화되고 로맨스 표준 시간대의 시스템에서 역직렬화되는 경우 예제에서는 다음 출력을 표시합니다.
'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월 10일인 3월 둘째 일요일에 발생합니다. 다음 예제에서 볼 수 있듯이 태평양 표준시 표준 시간대의 시스템에서 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시간을 올바르게 추가하기 위해 다음 세 단계를 수행한다는 점을 제외하고 이전 예제와 비슷합니다.
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)
인수로 포함하는 숫자 형식의 메서드를 사용합니다. - 복합 서식을 사용하여 숫자 값을 표시합니다.
다음은 프랑스 파리의 월 평균 온도를 표시하는 예제입니다. 먼저 데이터를 표시하기 전에 현재 문화권을 프랑스어(프랑스)로 설정한 다음 영어(미국)로 설정합니다. 각 경우에 월 이름과 온도는 해당 문화권에 적합한 형식으로 표시됩니다. 두 문화권은 온도 값에 서로 다른 소수 구분 기호를 사용합니다. 또한 이 예제에서는 "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개의 임의 부동 소수점 숫자를 생성한 다음 영어(미국) 문화권의 서식 규칙을 사용하여 문자열로 serialize합니다. 영어(미국) 문화권의 규칙을 사용하여 데이터를 검색하고 구문 분석하면 성공적으로 복원됩니다. 그러나 프랑스(프랑스) 문화권의 규칙을 사용하여 검색 및 구문 분석하는 경우 문화권이 서로 다른 소수 구분 기호를 사용하기 때문에 숫자를 구문 분석할 수 없습니다.
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) 하여 이러한 사용자 지정을 반영하는지 여부를 확인할 수 있습니다. 일반적으로 최종 사용자 앱의 경우 사용자가 예상한 형식으로 데이터를 표시할 수 있도록 사용자 기본 설정을 준수해야 합니다.
참고하십시오
.NET