[C#] 시간대를 우아하게 다루는 방법

응용 프로그램을 사용하는 사용자와 다른 시간대로 호스팅되는 웹 사이트가 있습니다. 이 외에도 사용자는 특정 시간대를 가질 수 있습니다. 다른 SO 사용자와 응용 프로그램이 어떻게 접근하는지 궁금합니다. 가장 분명한 부분은 DB 내부에서 날짜 / 시간이 UTC로 저장된다는 것입니다. 서버에있을 때 모든 날짜 / 시간은 UTC로 처리해야합니다. 그러나 극복하려는 세 가지 문제가 있습니다.

  1. UTC로 현재 시간을 가져옵니다 (로 쉽게 해결 DateTime.UtcNow).

  2. 데이터베이스에서 날짜 / 시간을 가져 와서 사용자에게 표시합니다. 다른보기에서 날짜를 인쇄하기위한 많은 호출 이있을 수 있습니다. 이 문제를 해결할 수있는 뷰와 컨트롤러 사이의 일부 레이어를 생각하고있었습니다. 또는 사용자 정의 확장 방법을 사용 중입니다 DateTime(아래 참조). 주요 단점은 뷰에서 날짜 시간을 사용하는 모든 위치에서 확장 메서드를 호출해야한다는 것입니다!

    또한와 같은 것을 사용하는 데 어려움이 있습니다 JsonResult. 당신은 더 이상 쉽게 부를 수있는 Json(myEnumerable)이 될 것이다, Json(myEnumerable.Select(transformAllDates)). 이 상황에서 AutoMapper가 도움이 될 수 있습니까?

  3. 사용자로부터 입력 받기 (Local to UTC). 예를 들어, 날짜가있는 양식을 게시하려면 날짜를 UTC로 변환해야합니다. 가장 먼저 떠오르는 것은 커스텀을 만드는 것입니다 ModelBinder.

뷰에서 사용하려고 생각한 확장 기능은 다음과 같습니다.

public static class DateTimeExtensions
{
    public static DateTime UtcToLocal(this DateTime source,
        TimeZoneInfo localTimeZone)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
    }

    public static DateTime LocalToUtc(this DateTime source,
        TimeZoneInfo localTimeZone)
    {
        source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
        return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
    }
}

시간대를 다루는 것은 서버의 현지 시간이 예상 시간대와 크게 다를 수있는 많은 응용 프로그램이 이제 클라우드 기반이라는 점을 고려하면 흔한 일이라고 생각합니다.

이것은 이전에 우아하게 해결 되었습니까? 내가 놓친 것이 있습니까? 아이디어와 생각은 대단히 감사합니다.

편집 : 혼란을 없애기 위해 몇 가지 세부 사항을 추가한다고 생각했습니다. 현재 문제 는 db에 UTC 시간을 저장 하는 방법 이 아니라 UTC-> 로컬 및 로컬-> UTC에서 진행하는 프로세스에 관한 것입니다. @Max Zerbini가 지적했듯이 UTC-> 로컬 코드를보기에 넣는 것이 현명하지만 DateTimeExtensions실제로 답을 사용하고 있습니까? 사용자로부터 입력을받을 때 날짜를 사용자의 현지 시간 (JS가 사용하는 시간이므로)으로 수락 한 다음 a ModelBinder를 사용 하여 UTC로 변환 하는 것이 합리적 입니까? 사용자 시간대는 DB에 저장되며 쉽게 검색 할 수 있습니다.



답변

이것이 권장 사항이 아니라 패러다임을 공유하는 것이 아니라 웹 응용 프로그램에서 시간대 정보를 처리 하는 가장 공격적인 방법은 다음과 같습니다 (ASP.NET MVC에만 해당되지 않음).

  • 서버의 모든 날짜 시간은 UTC입니다. 그것은 당신이 말한 것처럼 사용하는 것을 의미 DateTime.UtcNow합니다.

  • 클라이언트가 서버에 날짜를 가능한 한 적게 전달하도록하십시오. 예를 들어 “지금”이 필요한 경우 클라이언트에서 날짜를 만든 다음 서버로 전달하지 마십시오. GET에 날짜를 만들어 ViewModel 또는 POST에 전달하십시오 DateTime.UtcNow.

지금까지는 꽤 표준 요금이지만, 여기서는 ‘흥미로운’것들이 있습니다.

  • 클라이언트에서 날짜를 수락해야하는 경우, javascript를 사용하여 서버에 게시하는 데이터가 UTC로되어 있는지 확인하십시오. 클라이언트는 현재 시간대를 알고 있으므로 합리적인 시간으로 UTC로 변환 할 수 있습니다.

  • 뷰를 렌더링 할 때 HTML5 <time>요소를 사용하는 경우 ViewModel에서 직접 날짜 시간을 렌더링하지 않습니다. HtmlHelper확장 프로그램 으로 구현되었습니다 Html.Time(Model.when). 렌더링 <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>합니다.

    그런 다음 자바 스크립트를 사용하여 UTC 시간을 클라이언트 현지 시간으로 변환합니다. 스크립트는 모든 <time>요소를 찾고 date-formatdata 속성을 사용하여 날짜를 형식화하고 요소의 내용을 채 웁니다.

이러한 방식으로 고객 시간대를 추적, 저장 또는 관리 할 필요가 없었습니다. 서버는 클라이언트의 시간대를 신경 쓰지 않았으며 시간대 변환도 수행하지 않았습니다. 그것은 단순히 UTC를 뱉어 내고 클라이언트가 합리적인 것으로 변환하게합니다. 어떤 시간대에 있는지 알기 때문에 브라우저에서 쉽게 사용할 수 있습니다. 클라이언트가 시간대를 변경하면 웹 응용 프로그램이 자동으로 업데이트됩니다. 그들이 저장 한 유일한 것은 사용자의 로케일에 대한 날짜 시간 형식 문자열이었습니다.

나는 그것이 최선의 접근 방법이라고 말하지는 않지만 이전에는 보지 못했던 다른 접근 방법이었습니다. 아마도 당신은 그것으로부터 흥미로운 아이디어를 얻을 것입니다.


답변

몇 번의 피드백을 거친 후 깨끗하고 단순하며 일광 절약 문제를 다루는 최종 솔루션이 있습니다.

1-모델 수준에서 변환을 처리합니다. 따라서 Model 클래스에서 다음과 같이 작성합니다.

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2-글로벌 헬퍼에서 커스텀 함수 “ToLocalTime”을 만듭니다 :

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3-상수 “China Standard Time”을 사용하는 대신 사용자 클래스에서 검색 할 수 있도록 각 사용자 프로필에 시간대 ID를 저장하여이를 더욱 향상시킬 수 있습니다.

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4-드롭 다운 박스에서 선택하도록 사용자에게 보여줄 시간대 목록을 얻을 수 있습니다.

public class ListHelper
{
    public IEnumerable<SelectListItem> GetTimeZoneList()
    {
        var list = from tz in TimeZoneInfo.GetSystemTimeZones()
                   select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };

        return list;
    }
}

이제 중국에서 오전 9시 25 분, 미국에서 호스팅되는 웹 사이트, 데이터베이스에서 UTC로 저장된 날짜는 다음과 같습니다.

5/9/2013 6:25:58 PM (Server - in USA)
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)

편집하다

원래 솔루션의 약한 부분을 지적한 Matt Johnson 에게 감사하고 원래 게시물을 삭제 한 것에 대해 죄송하지만 올바른 코드 표시 형식을 얻는 데 문제가 있습니다 … 편집기에 “bullet”과 “pre code”를 혼합하는 데 문제가있는 것으로 나타났습니다. 불스를 제거하고 괜찮습니다.


답변

sf4answers이벤트 섹션 에서 사용자는 이벤트의 주소와 시작 날짜 및 선택적인 종료 날짜를 입력합니다. 이 시간은 UTC로부터의 오프셋을 설명하는 SQL 서버 로 변환됩니다 .datetimeoffset

이것은 당신이 직면 한 것과 같은 문제입니다 (당신이 그것을 사용한다는 점에서 다른 접근법을 취하고 있지만 DateTime.UtcNow); 위치가 있고 한 시간대에서 다른 시간대로 시간을 변환해야합니다.

나를 위해 일한 두 가지 주요 작업이 있습니다. 먼저, 항상 DateTimeOffsetstructure를 사용하십시오 . UTC로부터의 오프셋을 설명하며 클라이언트에서 해당 정보를 얻을 수 있으면 인생을 조금 더 쉽게 만듭니다.

둘째, 번역을 수행 할 때 클라이언트가있는 위치 / 시간대를 알고 있다고 가정하면 공개 정보 표준 시간대 데이터베이스 를 사용하여 UTC에서 다른 표준 시간대로 시간을 변환 할 수 있습니다. 시간대). tz 데이터베이스 ( Olson 데이터베이스 라고도 함)의 가장 큰 장점은 역사 전체의 시간대 변경을 설명한다는 것입니다. 오프셋을 얻는 것은 오프셋을 설정하려는 날짜의 함수입니다 ( 미국의 일광 절약 시간이 적용되는 날짜변경 한 2005 년 에너지 정책 법 참조).

데이터베이스를 보유하고 있으면 ZoneInfo (tz 데이터베이스 / Olson 데이터베이스) .NET API를 사용할 수 있습니다 . 바이너리 배포판이 없으므로 최신 버전 을 다운로드하여 직접 컴파일해야합니다.

이 글을 쓰는 시점에서 현재 최신 데이터 배포의 모든 파일을 구문 분석합니다 ( 9 월 25 일에 ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz 파일에 대해 실제로 실행했습니다 . 2011 년 3 월에는 https://iana.org/time-zones 또는 ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz 에서 얻을 수 있습니다 .

따라서 sf4answers에서 주소를 얻은 후 위도 / 경도 조합으로 지오 코딩 된 다음 타사 웹 서비스로 전송되어 tz 데이터베이스의 항목에 해당하는 시간대를 가져옵니다. 여기에서 시작 및 종료 시간이 DateTimeOffset적절한 UTC 오프셋을 가진 인스턴스로 변환 된 다음 데이터베이스에 저장됩니다.

SO 및 웹 사이트에서 처리하는 것은 대상과 표시하려는 대상에 따라 다릅니다. 대부분의 소셜 웹 사이트 (및 SO 및 sf4answers의 이벤트 섹션)는 상대적 시간 으로 이벤트를 표시 하거나 절대 값을 사용하는 경우 일반적으로 UTC입니다.

그러나 잠재 고객이 현지 시간을 예상하는 경우 DateTimeOffset시간대를 변환하는 확장 방법과 함께 사용 하면 문제가 없습니다. SQL 데이터 형식 datetimeoffset은 .NET으로 변환 DateTimeOffset되며이 GetUniversalTime메서드 를 사용하는 데 필요한 표준 시간을 얻을 수 있습니다 . 거기에서 ZoneInfo클래스 의 메소드를 사용하여 UTC에서 현지 시간으로 변환하기 만하면 됩니다 (로 가져 오기 위해 약간의 작업을 수행해야 DateTimeOffset하지만 간단합니다).

변환은 어디에서해야합니까? 그것은 당신이 어딘가에 지불해야 할 비용 이며, “최상의”방법은 없습니다. 그러나 뷰 모델의 일부로 시간대 오프셋이 뷰에 표시되는 뷰를 선택했습니다. 이렇게하면 뷰 요구 사항이 변경되면 변경 사항을 수용하기 위해 뷰 모델을 변경할 필요가 없습니다. 당신 JsonResult은 단순히 오프셋이 있는 모델을 포함합니다 .IEnumerable<T>

입력면에서 모델 바인더를 사용합니까? 나는 절대로 아무 말도하지 않을 것입니다. 모든 날짜 (현재 또는 미래)가 이러한 방식으로 변환되어야한다고 보장 할 수는 없습니다 .이 작업을 수행하려면 컨트롤러의 명시적인 기능이어야합니다. 요구 사항이 변경되면 ModelBinder비즈니스 논리를 조정하기 위해 하나 이상의 인스턴스를 조정할 필요가 없습니다 . 그것은 이 제어기에 있어야 수단 비즈니스 로직.


답변

이것은 단지 제 의견입니다. MVC 응용 프로그램은 데이터 프레젠테이션 문제와 데이터 모델 관리를 분리해야한다고 생각합니다. 데이터베이스는 로컬 서버 시간에 데이터를 저장할 수 있지만 로컬 사용자 시간대를 사용하여 날짜 시간을 렌더링하는 것은 프리젠 테이션 계층의 의무입니다. 이것은 나에게 I18N과 같은 문제와 다른 국가의 숫자 형식으로 보입니다. 귀하의 경우 응용 프로그램은 Culture사용자의 시간대를 감지하고 다른 텍스트, 숫자 및 주간 프리젠 테이션을 표시하는보기를 변경해야하지만 저장된 데이터는 동일한 형식을 가질 수 있습니다.


답변

출력하려면 다음과 같이 디스플레이 / 편집기 템플릿을 만드십시오.

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

특정 모델 만 해당 템플릿을 사용하도록하려면 모델의 속성을 기반으로 속성을 바인딩 할 수 있습니다.

사용자 정의 편집기 템플릿 작성에 대한 자세한 내용 은 여기여기 를 참조 하십시오 .

또는 입력과 출력 모두에서 작동하기를 원하기 때문에 컨트롤을 확장하거나 직접 만들 수도 있습니다. 이렇게하면 입력과 출력을 모두 가로 채고 필요에 따라 텍스트 / 값을 변환 할 수 있습니다.

당신이 그 길을 가고 싶다면 이 링크 가 희망적으로 올바른 방향으로 당신을 밀어 것입니다.

어느 쪽이든, 우아한 솔루션을 원한다면 약간의 작업이 될 것입니다. 밝은면에서 일단 완료하면 나중에 사용하기 위해 코드 라이브러리에 보관할 수 있습니다!


답변

이것은 아마도 너트를 깨뜨리는 슬레지 해머이지만 UI와 비즈니스 계층 사이에 날짜 시간을 반환 된 객체 그래프의 현지 시간으로, 입력 날짜 시간 매개 변수의 UTC로 투명하게 변환하는 레이어를 삽입 할 수 있습니다.

PostSharp 또는 일부 반전 제어 컨테이너를 사용 하여이 작업을 수행 할 수 있다고 생각합니다.

개인적으로 UI에서 날짜 시간을 명시 적으로 변환하는 것입니다 …


답변