방법: 웹 사용자에게 지역화된 날짜 및 시간 정보 표시

웹 페이지는 세계 어디서나 표시될 수 있기 때문에 날짜 및 시간 값을 구문 분석하고 형식을 지정하는 작업에서는 사용자와 상호 작용할 때 기본 형식(대개 웹 서버의 현지 문화권 형식)을 사용해서는 안 됩니다. 대신 사용자가 입력한 날짜 및 시간 문자열을 처리하는 Web Forms에서는 사용자의 기본 설정 문화권을 사용하여 문자열을 구문 분석해야 합니다. 마찬가지로 날짜 및 시간 데이터는 사용자 문화권에 맞는 형식으로 사용자에게 표시되어야 합니다. 이 항목에서는 이를 수행하는 방법에 대해 설명합니다.

사용자가 입력한 날짜 및 시간 문자열을 구문 분석하려면

  1. HttpRequest.UserLanguages 속성이 반환하는 문자열 배열이 채워져 있는지 확인합니다. 배열이 채워져 있지 않으면 6단계로 이동합니다.

  2. UserLanguages 속성이 반환하는 문자열 배열이 채워져 있으면 배열의 첫 번째 요소를 검색합니다. 첫 번째 요소는 사용자의 기본(기본 설정) 언어 및 지역을 나타냅니다.

  3. CultureInfo.CultureInfo(String, Boolean) 생성자를 호출하여 사용자의 기본 설정 문화권을 나타내는 CultureInfo 개체를 인스턴스화합니다.

  4. DateTime 또는 DateTimeOffset 형식의 TryParse 또는 Parse 메서드를 호출하여 변환을 시도합니다. TryParse 또는 Parse 메서드의 오버로드를 provider 매개 변수와 함께 사용하고 다음 중 하나를 전달합니다.

  5. 변환이 실패하면 UserLanguages 속성에서 반환되는 문자열 배열에 있는 나머지 각 요소에 대해 2단계에서 4단계를 반복합니다.

  6. 그래도 변환이 실패하거나 UserLanguages 속성에서 반환하는 문자열 배열이 비어 있으면 CultureInfo.InvariantCulture 속성에서 반환하는 고정 문화권을 사용하여 문자열을 구문 분석합니다.

사용자 요청의 현지 날짜 및 시간을 구문 분석하려면

  1. Web Form에 HiddenField 컨트롤을 추가합니다.

  2. 현재 날짜 및 시간과 UTC(협정 세계시)에 대한 현지 표준 시간대의 오프셋을 Value 속성에 쓰는 방법으로 Submit 단추의 onClick 이벤트를 처리하는 JavaScript 함수를 만듭니다. 세미콜론 등의 구분 기호를 사용하여 문자열의 두 구성 요소를 구분합니다.

  3. Web Form의 PreRender 이벤트를 사용하여 스크립트 텍스트를 ClientScriptManager.RegisterClientScriptBlock(Type, String, String, Boolean) 메서드에 전달함으로써 함수를 HTML 출력 스트림에 삽입합니다.

  4. JavaScript 함수의 이름을 Submit 단추의 OnClientClick 특성에 제공하여 이벤트 처리기를 Submit 단추의 onClick 이벤트에 연결합니다.

  5. Submit 단추의 Click 이벤트에 대한 처리기를 만듭니다.

  6. 이벤트 처리기에서 HttpRequest.UserLanguages 속성이 반환하는 문자열 배열이 채워져 있는지 확인합니다. 배열이 채워져 있지 않으면 14단계로 이동합니다.

  7. UserLanguages 속성이 반환하는 문자열 배열이 채워져 있으면 배열의 첫 번째 요소를 검색합니다. 첫 번째 요소는 사용자의 기본(기본 설정) 언어 및 지역을 나타냅니다.

  8. CultureInfo.CultureInfo(String, Boolean) 생성자를 호출하여 사용자의 기본 설정 문화권을 나타내는 CultureInfo 개체를 인스턴스화합니다.

  9. Value 속성에 할당된 문자열을 Split 메서드로 전달하여 사용자의 현지 날짜 및 시간에 대한 문자열 표현과 사용자의 현지 표준 시간대 오프셋에 대한 문자열 표현을 각각 별도의 배열 요소에 저장합니다.

  10. DateTime.Parse 또는 DateTime.TryParse(String, IFormatProvider, DateTimeStyles, DateTime) 메서드를 호출하여 사용자 요청의 날짜 및 시간을 DateTime 값으로 변환합니다. 메서드의 오버로드를 provider 매개 변수와 함께 사용하고 다음 중 하나를 전달합니다.

  11. 10단계의 구문 분석 작업이 실패하면 13단계로 이동하고, 그렇지 않으면 UInt32.Parse(String) 메서드를 호출하여 사용자 표준 시간대 오프셋의 문자열 표현을 정수로 변환합니다.

  12. DateTimeOffset.DateTimeOffset(DateTime, TimeSpan) 생성자를 호출하여 사용자의 현지 시간을 나타내는 DateTimeOffset을 인스턴스화합니다.

  13. 10단계의 변환이 실패하면 UserLanguages 속성에서 반환되는 문자열 배열에 있는 나머지 각 요소에 대해 7단계에서 12단계를 반복합니다.

  14. 그래도 변환이 실패하거나 UserLanguages 속성에서 반환하는 문자열 배열이 비어 있으면 CultureInfo.InvariantCulture 속성에서 반환하는 고정 문화권을 사용하여 문자열을 구문 분석합니다. 그런 다음 7단계부터 12단계까지 반복합니다.

그러면 웹 페이지 사용자의 현지 시간을 나타내는 DateTimeOffset 개체가 생성됩니다. 여기서 ToUniversalTime 메서드를 호출하여 해당 UTC를 확인할 수 있습니다. 또한 TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo) 메서드를 호출하고 TimeZoneInfo.Local의 값을 시간을 변환할 표준 시간대로 전달하여 웹 서버에서 해당하는 날짜 및 시간을 확인할 수도 있습니다.

예제

다음 예제에는 사용자에게 날짜 및 시간 값을 입력하라는 메시지를 표시하는 ASP.NET Web Form의 HTML 소스 및 코드가 모두 포함되어 있습니다. 또한 클라이언트 쪽 스크립트는 사용자 요청의 현지 날짜 및 시간과 UTC에 대한 사용자 표준 시간대의 오프셋을 숨겨진 필드에 씁니다. 이 정보는 서버에서 구문 분석되며 서버에서는 사용자 입력을 표시하는 웹 페이지를 반환합니다. 또한 사용자의 현지 시간, 서버의 시간 및 UTC를 사용하여 사용자 요청의 날짜 및 시간이 표시됩니다.


<%@ Page Language="VB" %>
<%@ Import  Namespace="System.Globalization" %> 
<%@ Assembly Name="System.Core" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    Protected Sub OKButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles OKButton.Click
        Dim locale As String = ""
        Dim styles As DateTimeStyles = DateTimeStyles.AllowInnerWhite Or DateTimeStyles.AllowLeadingWhite Or _
                                       DateTimeStyles.AllowTrailingWhite
        Dim inputDate, localDate As Date
        Dim localDateOffset As DateTimeOffset
        Dim integerOffset As Integer
        Dim result As Boolean

        ' Exit if input is absent.
        If String.IsNullOrEmpty(Me.DateString.Text) Then Exit Sub

        ' Hide form elements.
        Me.DateForm.Visible = False

        ' Create array of CultureInfo objects
        Dim cultures(Request.UserLanguages.Length) As CultureInfo
        For ctr As Integer = Request.UserLanguages.GetLowerBound(0) To Request.UserLanguages.GetUpperBound(0)
            locale = Request.UserLanguages(ctr)
            If Not String.IsNullOrEmpty(locale) Then
                ' Remove quality specifier, if present.
                If locale.Contains(";") Then _
                   locale = Left(locale, InStr(locale, ";") - 1)
                Try
                   cultures(ctr) = New CultureInfo(Request.UserLanguages(ctr), False)
                Catch
                End Try
            Else
                cultures(ctr) = CultureInfo.CurrentCulture
            End If
        Next
        cultures(Request.UserLanguages.Length) = CultureInfo.InvariantCulture
        ' Parse input using each culture.
        For Each culture As CultureInfo In cultures
            result = Date.TryParse(Me.DateString.Text, culture.DateTimeFormat, styles, inputDate)
            If result Then Exit For
        Next
        ' Display result to user.
        If result Then
            Response.Write("<P />")
            Response.Write("The date you input was " + Server.HtmlEncode(CStr(Me.DateString.Text)) + "<BR />")
        Else
            ' Unhide form.
            Me.DateForm.Visible = True
            Response.Write("<P />")
            Response.Write("Unable to recognize " + Server.HtmlEncode(Me.DateString.Text) + ".<BR />")
        End If

        ' Get date and time information from hidden field.
        Dim dates() As String = Request.Form.Item("DateInfo").Split(";")

        ' Parse local date using each culture.
        For Each culture As CultureInfo In cultures
            result = Date.TryParse(dates(0), culture.DateTimeFormat, styles, localDate)
            If result Then Exit For
        Next
        ' Parse offset 
        result = Integer.TryParse(dates(1), integerOffset)
        ' Instantiate DateTimeOffset object representing user's local time
        If result Then
            Try
                localDateOffset = New DateTimeOffset(localDate, New TimeSpan(0, -integerOffset, 0))
            Catch ex As Exception
                result = False
            End Try
        End If
        ' Display result to user.
        If result Then
            Response.Write("<P />")
            Response.Write("Your local date and time is " + localDateOffset.ToString() + ".<BR />")
            Response.Write("The date and time on the server is " & _
                           TimeZoneInfo.ConvertTime(localDateOffset, _
                                                    TimeZoneInfo.Local).ToString() & ".<BR />")
            Response.Write("Coordinated Universal Time is " + localDateOffset.ToUniversalTime.ToString() + ".<BR />")
        Else
            Response.Write("<P />")
            Response.Write("Unable to recognize " + Server.HtmlEncode(dates(0)) & ".<BR />")
        End If
    End Sub

    Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
        Dim script As String = "function AddDateInformation() { " & vbCrLf & _
                  "var today = new Date();" & vbCrLf & _
                  "document.DateForm.DateInfo.value = today.toLocaleString() + " & Chr(34) & Chr(59) & Chr(34) & " + today.getTimezoneOffset();" & vbCrLf & _
                  " }"
        ' Register client script
        Dim scriptMgr As ClientScriptManager = Page.ClientScript
        scriptMgr.RegisterClientScriptBlock(Me.GetType(), "SubmitOnClick", script, True)
    End Sub
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Parsing a Date and Time Value</title>
</head>
<body>
    <form id="DateForm" runat="server">
    <div>
    <center>
       <asp:Label ID="Label1" runat="server" Text="Enter a Date and Time:" Width="248px"></asp:Label>
       <asp:TextBox ID="DateString" runat="server" Width="176px"></asp:TextBox><br />
    </center>       
    <br />
    <center>
    <asp:Button ID="OKButton" runat="server" Text="Button"  
            OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
    <asp:HiddenField ID="DateInfo"  Value=""  runat="server" />
    </center>
    <br />
    </div>
    </form>
</body>
</html>

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Assembly Name="System.Core" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void OKButton_Click(object sender, EventArgs e)
    {
        string locale = "";
        DateTimeStyles styles = DateTimeStyles.AllowInnerWhite | DateTimeStyles.AllowLeadingWhite |
                                       DateTimeStyles.AllowTrailingWhite;
        DateTime inputDate;
        DateTime localDate = DateTime.Now;
        DateTimeOffset localDateOffset = DateTimeOffset.Now;
        int integerOffset;
        bool result = false;

        // Exit if input is absent.
        if (string.IsNullOrEmpty(this.DateString.Text)) return;

        // Hide form elements.
        this.DateForm.Visible = false;

        // Create array of CultureInfo objects
        CultureInfo[] cultures = new CultureInfo[Request.UserLanguages.Length + 1];
        for (int ctr = Request.UserLanguages.GetLowerBound(0); ctr <= Request.UserLanguages.GetUpperBound(0);
             ctr++)
        {
            locale = Request.UserLanguages[ctr];
            if (! string.IsNullOrEmpty(locale))
            {

                // Remove quality specifier, if present.
                if (locale.Contains(";"))
                   locale = locale.Substring(locale.IndexOf(';') -1);
                try
                {
                    cultures[ctr] = new CultureInfo(Request.UserLanguages[ctr], false);
                }
                catch (Exception) { }
            }
            else
            {
                cultures[ctr] = CultureInfo.CurrentCulture;
            }
        }
        cultures[Request.UserLanguages.Length] = CultureInfo.InvariantCulture;
        // Parse input using each culture.
        foreach (CultureInfo culture in cultures)
        {
            result = DateTime.TryParse(this.DateString.Text, culture.DateTimeFormat, styles, out inputDate);
            if (result) break;
        }
        // Display result to user.
        if (result)
        {
            Response.Write("<P />");
            Response.Write("The date you input was " + Server.HtmlEncode(this.DateString.Text) + "<BR />");
        }
        else
        {
            // Unhide form.
            this.DateForm.Visible = true;
            Response.Write("<P />");
            Response.Write("Unable to recognize " + Server.HtmlEncode(this.DateString.Text) + ".<BR />");
        }

        // Get date and time information from hidden field.
        string[] dates= Request.Form["DateInfo"].Split(';');

        // Parse local date using each culture.
        foreach (CultureInfo culture in cultures)
        {
            result = DateTime.TryParse(dates[0], culture.DateTimeFormat, styles, out localDate);
            if (result) break;
        }
        // Parse offset 
        result = int.TryParse(dates[1], out integerOffset);
        // Instantiate DateTimeOffset object representing user's local time
        if (result) 
        {
            try
            {
                localDateOffset = new DateTimeOffset(localDate, new TimeSpan(0, -integerOffset, 0));
            }
            catch (Exception)
            {
                result = false;
            }
        }
        // Display result to user.
        if (result)
        {
            Response.Write("<P />");
            Response.Write("Your local date and time is " + localDateOffset.ToString() + ".<BR />");
            Response.Write("The date and time on the server is " + 
                           TimeZoneInfo.ConvertTime(localDateOffset, 
                                                    TimeZoneInfo.Local).ToString() + ".<BR />");
            Response.Write("Coordinated Universal Time is " + localDateOffset.ToUniversalTime().ToString() + ".<BR />");
        }
        else
        {
            Response.Write("<P />");
            Response.Write("Unable to recognize " + Server.HtmlEncode(dates[0]) + ".<BR />");
        }
    }

    protected void Page_PreRender(object sender, System.EventArgs e)
    {
        string script = "function AddDateInformation() { \n" +
                  "var today = new Date();\n" +
                  "document.DateForm.DateInfo.value = today.toLocaleString() + \";\" + today.getTimezoneOffset();\n" +
                  " }";
        // Register client script
        ClientScriptManager scriptMgr = Page.ClientScript;
        scriptMgr.RegisterClientScriptBlock(this.GetType(), "SubmitOnClick", script, true);
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Parsing a Date and Time Value</title>
</head>
<body>
    <form id="DateForm" runat="server">
    <div>
    <center>
       <asp:Label ID="Label1" runat="server" Text="Enter a Date and Time:" Width="248px"></asp:Label>
       <asp:TextBox ID="DateString" runat="server" Width="176px"></asp:TextBox><br />
    </center>       
    <br />
    <center>
    <asp:Button ID="OKButton" runat="server" Text="Button"  
            OnClientClick="AddDateInformation()" onclick="OKButton_Click" />
    <asp:HiddenField ID="DateInfo"  Value=""  runat="server" />
    </center>
    <br />
    </div>
    </form>
</body>
</html>

클라이언트 쪽 스크립트에서는 JavaScript toLocaleString 메서드를 호출합니다. 그러면 사용자 로캘의 서식 지정 규칙을 따르는 문자열이 생성되며, 이 문자열은 서버에서 성공적으로 구문 분석될 가능성이 더 높습니다.

HttpRequest.UserLanguages 속성은 HTTP 요청에 포함된 Accept-Language 헤더에 들어 있는 문화권 이름에서 채워집니다. 그러나 모든 브라우저의 요청에 Accept-Language 헤더가 들어 있는 것은 아니며 사용자가 헤더를 완전히 표시하지 않을 수도 있습니다. 그러므로 사용자 입력을 구문 분석할 때는 대체(fallback) 문화권이 있어야 합니다. 일반적으로 대체(fallback) 문화권은 CultureInfo.InvariantCulture에서 반환하는 고정 문화권입니다. 사용자는 텍스트 상자에 문화권 이름을 입력하여 Internet Explorer에 대해 문화권 이름을 제공할 수도 있지만, 이렇게 하면 문화권 이름이 유효하지 않게 될 수도 있습니다. 그러므로 CultureInfo 개체를 인스턴스화할 때는 예외 처리를 사용해야 합니다.

Internet Explorer에서 전송한 HTTP 요청에서 검색되는 HttpRequest.UserLanguages 배열은 사용자가 기본 설정한 순서대로 채워집니다. 배열의 첫 번째 요소에는 사용자의 기본 문화권/지역 이름이 포함됩니다. 배열에 항목이 추가로 포함된 경우에는 Internet Explorer에서 이러한 항목에 품질 지정자를 임의로 할당합니다. 품질 지정자는 세미콜론을 사용하여 문화권 이름과 구분됩니다. 예를 들어 fr-FR 문화권에 대한 항목은 fr-FR;q=0.7 형식일 수 있습니다.

이 예제에서는 useUserOverride 매개 변수를 false로 설정하고 CultureInfo 생성자를 호출하여 새 CultureInfo 개체를 만듭니다. 이렇게 하면 문화권 이름이 서버의 기본 문화권 이름인 경우 클래스 생성자에서 만드는 새 CultureInfo 개체에 문화권의 기본 설정이 포함되고 서버의 국가 및 언어 옵션을 사용하여 재정의한 모든 설정이 반영되지 않습니다. 서버에서 재정의된 설정 값은 사용자 시스템에 없거나 사용자 입력에 반영되지 않습니다.

이 예제에서는 날짜 및 시간의 두 문자열 표현(사용자의 입력과 숨겨진 필드에 저장된 입력)을 구문 분석하므로 사전에 필요할 수 있는 CultureInfo 개체를 정의합니다. 즉, HttpRequest.UserLanguages 속성에서 반환하는 요소의 수보다 큰 CultureInfo 개체의 배열이 만들어집니다. 그런 다음 각 언어/지역 문자열에 대해 CultureInfo 개체가 인스턴스화되며 CultureInfo.InvariantCulture를 나타내는 CultureInfo 개체도 인스턴스화됩니다.

코드에서 Parse 또는 TryParse 메서드를 호출하여 사용자의 날짜 및 시간 문자열 표현을 DateTime 값으로 변환할 수 있습니다. 한 번의 구문 분석 작업을 위해 구문 분석 메서드를 반복해서 호출해야 할 수 있습니다. 그러므로 구문 분석 작업이 실패하면 false를 반환하는 TryParse 메서드를 사용하는 것이 보다 효과적입니다. 반면 Parse 메서드에서 throw할 수 있는 반복적인 예외를 처리하는 작업은 웹 응용 프로그램의 경우 매우 많은 비용이 소요될 수 있습니다.

코드 컴파일

코드를 컴파일하려면 코드를 숨기지 않고 ASP.NET 웹 페이지를 만듭니다. 그런 다음 모든 기존 코드 대신 이 항목의 예제를 웹 페이지에 복사합니다. 그러면 ASP.NET 웹 페이지에 다음 컨트롤이 포함됩니다.

  • 코드에서 참조되지 않는 Label 컨트롤. Text 속성을 "Enter a Number:"로 설정합니다.

  • DateString이라는 TextBox 컨트롤

  • OKButton이라는 Button 컨트롤. Text 속성을 "OK"로 설정합니다.

  • DateInfo라는 HiddenField 컨트롤

보안

사용자가 HTML 스트림에 스크립트를 삽입하지 못하게 하려면 사용자 입력을 서버 응답에서 직접 다시 에코해서는 안 됩니다. 이 경우에는 HttpServerUtility.HtmlEncode 메서드를 사용하여 입력을 인코딩해야 합니다.

참고 항목

개념

형식 지정 작업 수행

표준 날짜 및 시간 서식 문자열

사용자 지정 날짜 및 시간 형식 문자열

날짜 및 시간 문자열 구문 분석