시간대를 우아하게 다루는 방법


140

응용 프로그램을 사용하는 사용자와 다른 시간대로 호스팅되는 웹 사이트가 있습니다. 이 외에도 사용자는 특정 시간대를 가질 수 있습니다. 다른 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에 저장되며 쉽게 검색 할 수 있습니다.


3
먼저이 우수한 포스트 ... 통해 읽기가있을 수 있습니다 일광 절약 시간과 시간대 모범 사례
dodgy_coder

@dodgy_coder-항상 시간대에 대한 훌륭한 리소스 링크였습니다. 그러나 실제로는 내 문제 (특히 MVC 관련)를 해결하지 못합니다. 그래도 고마워.
TheCloudlessSky

code.google.com/p/noda-time 이 유용 할 것입니다.
Arnis Lapsa

어떤 솔루션을 선택했는지 궁금합니다. 비슷한 결정에 직면하고 있습니다. 좋은 질문.
Sean

@Sean-지금까지 해결책은 우아하지 않았습니다 (그래서 아직 대답을 받아들이지 않았습니다). 날짜 / 시간을 인쇄하고 수동으로 다시 변환하는 데 많은 수동 오버 헤드가 발생했습니다 ModelBinder.
TheCloudlessSky

답변:


106

이것이 권장 사항이 아니라 패러다임을 공유하는 것이 아니라 웹 응용 프로그램에서 시간대 정보를 처리 하는 가장 공격적인 방법은 다음과 같습니다 (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를 뱉어 내고 클라이언트가 합리적인 것으로 변환하게합니다. 어떤 시간대에 있는지 알기 때문에 브라우저에서 쉽게 사용할 수 있습니다. 클라이언트가 시간대를 변경하면 웹 응용 프로그램이 자동으로 업데이트됩니다. 그들이 저장 한 유일한 것은 사용자의 로케일에 대한 날짜 시간 형식 문자열이었습니다.

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


답변 주셔서 감사합니다. 사용자로부터 날짜 입력을받는 경우 (예 : 약속 예약)이 입력이 현재 시간대에 있다고 가정 하지 않습니까? 액션을 입력하기 전에 ModelBinder 가이 변환을 수행하는 것에 대해 어떻게 생각하십니까 (@casperOne에 대한 내 의견 참조) <time>아이디어는 정말 멋지지만 유일한 단점은 이러한 요소에 대해 전체 DOM을 쿼리해야한다는 것입니다. 날짜를 변환하는 것만으로는 좋지 않습니다 (JS는 어떻습니까?) 다시 감사합니다!
TheCloudlessSky

일반적으로 그렇습니다. 현지 시간대로 가정합니다. 그러나 그들은 사용자로부터 시간을 수집 할 때마다 서버로 전송되기 전에 자바 스크립트를 사용하여 UTC로 변환되었음을 확인했습니다. 그들의 솔루션은 매우 JS 무거웠으며 JS가 꺼져있을 때 아주 훌륭하게 저하되지 않았습니다. 나는 그 주위에 영리한 것이 있는지 알기에 충분히 생각하지 못했습니다. 그러나 내가 말했듯이 그것은 당신에게 몇 가지 아이디어를 줄 것입니다.
J. Holmes

2
그 후 현지 날짜가 필요한 다른 프로젝트를 수행했으며 이것이 내가 사용한 방법이었습니다. 날짜 / 시간 형식을 지정하기 위해 moment.js 를 사용 하고 있습니다 ... 달콤합니다. 감사!
TheCloudlessSky

응용 프로그램의 app.config / web.cofig에서 응용 프로그램 범위 시간대를 정의하고 모든 DateTime.Now 값을 DateTime.UtcNow로 만드는 간단한 방법이 없습니까? (I는 회사의 프로그래머는 여전히 실수로 DateTime.Now를 사용하는 것이 어디 상황을 피하려고)
열린 우리당 Abramson이

3
내가 볼 수있는이 접근법에 대한 한 가지 단점은 다른 것들에 시간을 사용하고 pdf, 이메일 등을 만들 때 시간 요소가 없으므로 수동으로 변환해야한다는 것입니다. 그렇지 않으면 매우 깔끔한 솔루션
shenku

15

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

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"를 혼합하는 데 문제가있는 것으로 나타났습니다. 불스를 제거하고 괜찮습니다.


n- 계층 아키텍처가 없거나 웹 라이브러리가 공유 된 경우 일부 실행 가능한 것 같습니다. 시간대 ID를 데이터 계층에 공유하는 방법
Vikash Kumar

9

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비즈니스 논리를 조정하기 위해 하나 이상의 인스턴스를 조정할 필요가 없습니다 . 그것은 이 제어기에 있어야 수단 비즈니스 로직.


자세한 답변 감사합니다. 무례한 일이 아니기를 바랍니다. 그러나 이것이 실제로 ASP.NET MVC에 대한 나의 관심사 중 어느 것도 대답하지 못했습니다 : 사용자 입력은 어떻습니까 (사용자 정의 모델 바인더로 충분할까요?)? 그리고 가장 중요한 것은 이 날짜를 사용자의 현지 시간대로 표시하는 것은 어떻습니까? 확장 방법이 불필요한 것처럼 보이는 내 견해에 많은 가중치를 추가 할 것 같습니다.
TheCloudlessSky

1
@TheCloudlessSky : 편집 한 응답의 마지막 두 단락을 참조하십시오. 개인적으로, 변환을 수행 할 위치에 대한 세부 사항은 미미하다고 생각합니다. 주요 문제는 날짜 시간 데이터의 실제 변환 및 저장입니다 (btw, datetimeoffsetSQL Server 및 DateTimeOffset.NET 에서 사용을 충분히 강조 할 수는 없지만 실제로는 일을 엄청나게 단순화합니다) .NET이 제대로 처리하지 못한다고 생각합니다 위에서 설명한 이유로 전혀. NYC에 2003 년에 입력 한 날짜가 있고 2011 년에 LA로 날짜를 변환하려는 경우이 경우 .NET이 실패합니다.
casperOne

모델 바인더 (또는 작업 전의 레이어)를 사용하지 않는 문제는 날짜를 다룰 때 모든 컨트롤러가 테스트하기 가 매우 어렵다는 것입니다 (모두 날짜 변환에 대한 의존성을 얻습니다). 이를 통해 단위 테스트 날짜를 UTC로 작성할 수 있습니다. 내 응용 프로그램에는 프로필과 관련된 사용자가 있습니다 (실습과 관련된 의사 / 비서관 생각). 프로파일은 시간대 정보를 보유합니다. 따라서 바인딩하는 동안 현재 사용자의 프로필 시간대를 가져오고 변환하는 것은 매우 쉽습니다. 이에 반대하는 다른 주장이 있습니까? 입력 해 주셔서 감사합니다!
TheCloudlessSky

그 반대의 경우는 테스트가 테스트 사례를 정확하게 반영하지 못한다는 것입니다. 귀하의 입력이되어 있지 UTC에있을 것, 그래서 당신의 테스트 케이스를 사용하여 제작 된되어서는 안된다. 실제 날짜와 위치 및 모든 날짜를 사용해야합니다 (물론 사용하는 경우 DateTimeOffset이를 크게 완화 할 수는 있지만 IMO).
casperOne

예-그러나 이것은 날짜를 다루는 모든 테스트가 이를 설명해야 함을 의미합니다 . 날짜 / 시간을 처리하는 모든 작업 은 항상 UTC 로 변환 됩니다. 나에게 이것은 모델 바인더의 주요 후보이며 모델 바인더를 테스트합니다 .
TheCloudlessSky

5

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


답변 주셔서 감사합니다. 예, 이것이 제가 기본적으로 설명한 것 입니다. MVC로 어떻게 이것이 우아하게 달성 될 수 있는지 궁금 합니다 . 앱은 사용자 시간대를 계정 생성의 일부로 저장합니다 (시간대 선택). 문제는 다시 만든 방법으로 날짜 를 버리지 않고 각보기에서 날짜를 렌더링 하는 방법에 관한 것입니다 DateTimeExtensions.
TheCloudlessSky

이 작업을 수행하는 방법에는 여러 가지가 있습니다. 하나는 제안한대로 도우미 메서드를 사용하는 것입니다. 또 다른 방법은 더 복잡하지만 우아하지만 요청과 응답을 처리하고 날짜 시간을 변환하는 필터를 사용하는 것입니다. 다른 방법은 뷰 모델의 DateTime 유형의 필드에 주석을 달기 위해 사용자 정의 Display 속성을 개발하는 것입니다.
Massimo Zerbini

그것들은 내가 찾던 것들입니다. 내 OP를 보면 AutoMapper를 사용 하여 view / json 결과로 이동하기 전에 그러나 작업이 실행 된 후 처리를 언급했습니다 .
TheCloudlessSky

1

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

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

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

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

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

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

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


커스텀 디스플레이 / 에디터 템플릿과 바인더는 일단 구현되면 "작동"하기 때문에 가장 우아한 솔루션이라고 생각합니다. 어떤 개발자는 바로 바로 사용 작업 날짜 얻기 위해 아무것도 특별한을 알 필요가 없습니다 DisplayForEditorFor그것은 모든 시간을 작동합니다. +1
Kevin Stricker

17
이것이 브라우저의 시간이 아닌 응용 프로그램 서버의 시간을 렌더링하지 않습니까?
Alex

0

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.