DST 이전 또는 이후 날짜의 UTC와 로컬 시간 사이의 올바른 오프셋을 얻으려면 어떻게해야합니까?


29

현재 UTC 날짜 시간에서 현지 날짜 시간을 가져 오기 위해 다음을 사용합니다.

SET @offset = DateDiff(minute, GetUTCDate(), GetDate())
SET @localDateTime = DateAdd(minute, @offset, @utcDateTime)

내 문제는 일광 절약 시간 사이에 발생하는 경우 GetUTCDate()@utcDateTime@localDateTime한 시간 떨어져있는 끝납니다.

현재 날짜가 아닌 날짜에 대해 utc에서 현지 시간으로 쉽게 변환 할 수 있습니까?

SQL Server 2005를 사용하고 있습니다

답변:


18

현재가 아닌 UTC 날짜를 현지 시간으로 변환하는 가장 좋은 방법은 CLR을 사용하는 것입니다. 코드 자체는 쉽다; 어려운 부분은 보통 사람들에게 CLR이 순수한 악이나 무서운 것이 아니라는 것을 설득하는 것입니다.

많은 예 중 하나에 대해서는 Harsh Chawla의 블로그 게시물 주제를 확인하십시오 .

불행히도 CLR 기반 솔루션을 제외하고 이러한 유형의 변환을 처리 할 수있는 내장 기능은 없습니다. 이와 같은 작업을 수행하는 T-SQL 함수를 작성할 수는 있지만 날짜 변경 논리를 직접 구현해야하며 결정하기 쉽지 않습니다.


시간이 지남에 따른 지역별 변화의 실제 복잡성을 감안할 때 순수한 T-SQL에서 이것을 시도하는 것이 "쉽지 않다"고 말하면 아마도 그것을 과소 평가하는 것입니다. ;-) 그렇습니다. SQLCLR은이 작업을 수행 할 수있는 유일한 안정적이고 효율적인 수단입니다. +1입니다. 참고 : 링크 된 블로그 게시물은 기능적으로 정확하지만 모범 사례를 따르지 않으므로 불행히도 비효율적입니다. UTC와 서버의 현지 시간 간 변환 기능은 SQL # 라이브러리 (저는 필자가 작성한)에서 사용할 수 있지만 무료 버전 에서는 사용할 수 없습니다 .
Solomon Rutzky 2016 년

1
추가해야 할 때 CLR이 악해집니다 WITH PERMISSION_SET = UNSAFE. 일부 환경에서는 AWS RDS와 같이 허용하지 않습니다. 그리고 그것은 안전하지 않습니다. 안타깝게도 unsafe권한 없이 사용할 수있는 .Net 표준 시간대 구현은 없습니다. 여기여기를 참조 하십시오 .
Frédéric

15

Microsoft SQL Server에서 날짜 시간 및 표준 시간대 처리에 어려움을 겪는 사람을 돕기 위해 코드 플렉스 에서 T-SQL Toolbox 프로젝트를 개발하고 게시했습니다 . 오픈 소스이며 완전 무료입니다.

미리 채워진 구성 테이블과 함께 일반 T-SQL (CLR 없음)을 사용하여 쉬운 날짜 / 시간 변환 UDF를 제공합니다. 또한 DST (일광 절약 시간제)를 완벽하게 지원합니다.

지원되는 모든 시간대 목록은 "DateTimeUtil.Timezone"테이블 (T-SQL Toolbox 데이터베이스 내에 제공)에서 찾을 수 있습니다.

귀하의 예에서 다음 샘플을 사용할 수 있습니다.

SELECT [DateTimeUtil].[UDF_ConvertUtcToLocalByTimezoneIdentifier] (
    'W. Europe Standard Time', -- the target local timezone
    '2014-03-30 00:55:00' -- the original UTC datetime you want to convert
)

변환 된 현지 날짜 / 시간 값을 반환합니다.

불행히도 최신 데이터 형식 (DATE, TIME, DATETIME2)으로 인해 SQL Server 2008 이상에서만 지원됩니다. 그러나 전체 소스 코드가 제공되므로 테이블과 UDF를 DATETIME으로 대체하여 쉽게 조정할 수 있습니다. 테스트에 사용할 수있는 MSSQL 2005는 없지만 MSSQL 2005에서도 작동합니다. 궁금한 점이 있으면 알려주세요.


12

항상이 TSQL 명령을 사용합니다.

-- the utc value 
declare @utc datetime = '20/11/2014 05:14'

-- the local time

select DATEADD(hh, DATEDIFF(hh, getutcdate(), getdate()), @utc)

매우 간단하고 작업을 수행합니다.


2
UTC와의 전체 시간 오프셋이 아닌 시간대가 있으므로이 DATEPART를 사용하면 문제가 발생할 수 있습니다.
Michael Green

4
Michael Green의 의견에 대해서는 SELECT DATEADD (MINUTE, DATEDIFF (MINUTE, GETUTCDATE (), GETDATE ()), @utc)로 변경하여 문제를 해결할 수 있습니다.
등록 된 사용자

4
현재 시간이 DST인지 여부를 확인한 다음 DST가 될 수있는 시간을 비교하기 때문에 작동하지 않습니다. 영국에서 위의 예제 코드와 날짜 시간을 사용하면 현재 오전 6시 14 분이어야하지만 11 월은 DST 외부에 있으므로 GMT와 UTC가 일치하므로 오전 5시 14 분이어야합니다.
Matt

동의하지 않는 한이 질문에 대한 실제 답변을 다루지는 않지만 다음과 같은 것이 더 좋다고 생각합니다. SELECT DATEADD (MINUTE, DATEPART (TZoffset, SYSDATETIMEOFFSET ()), @utc)
Eamon

@Ludo Bernaerts : 처음 사용할 때 밀리 초, 초 다음 UTC 오늘은 UTC 오프셋 특정 시간에 다를 수 있습니다 오프셋 때문에이 일을하지 않는 (일광 절약 - 겨울 시간 대 여름) ...
당혹

11

날짜 시간을 정확하게 번역하는 것처럼 보이는 사용자 정의 함수를 제공하는 StackOverflow 에서이 답변 을 찾았습니다.

@offset이 함수를 실행하는 SQL Server의 표준 시간대 오프셋으로 설정하기 위해 맨 위에 있는 변수 만 수정 하면됩니다. 필자의 경우 SQL 서버는 EST, 즉 GMT-5를 사용합니다.

완벽하지 않고 아마도 30 분 또는 15 분의 TZ 오프셋이있는 많은 경우에는 작동하지 않을 것입니다 ( 케빈이 권장하는 것과 같은 CLR 기능을 권장하는 사람들을 위해 ), 그러나 북의 대부분의 일반적인 시간대에는 충분히 잘 작동합니다 미국.

CREATE FUNCTION [dbo].[UDTToLocalTime](@UDT AS DATETIME)  
RETURNS DATETIME
AS
BEGIN 
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE @Offset AS SMALLINT
SET @Offset = -5

--====================================================
--Figure out the Offset Datetime
--====================================================
DECLARE @LocalDate AS DATETIME
SET @LocalDate = DATEADD(hh, @Offset, @UDT)

--====================================================
--Figure out the DST Offset for the UDT Datetime
--====================================================
DECLARE @DaylightSavingOffset AS SMALLINT
DECLARE @Year as SMALLINT
DECLARE @DSTStartDate AS DATETIME
DECLARE @DSTEndDate AS DATETIME
--Get Year
SET @Year = YEAR(@LocalDate)

--Get First Possible DST StartDay
IF (@Year > 2006) SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE              SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate 
WHILE (DATENAME(dw, @DSTStartDate) <> 'sunday') SET @DSTStartDate = DATEADD(day, 1,@DSTStartDate)


--Get First Possible DST EndDate
IF (@Year > 2006) SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE              SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate 
WHILE (DATENAME(dw, @DSTEndDate) <> 'sunday') SET @DSTEndDate = DATEADD(day,1,@DSTEndDate)

--Get DaylightSavingOffset
SET @DaylightSavingOffset = CASE WHEN @LocalDate BETWEEN @DSTStartDate AND @DSTEndDate THEN 1 ELSE 0 END

--====================================================
--Finally add the DST Offset 
--====================================================
RETURN DATEADD(hh, @DaylightSavingOffset, @LocalDate)
END



GO


3

Stack Overflow 와 비슷한 질문에 대한 몇 가지 좋은 답변이 있습니다. Bob Albright두 번째 답변 에서 T-SQL 접근 방식을 사용하여 데이터 변환 컨설턴트로 인한 혼란을 정리했습니다.

그것은 거의 모든 데이터에 적용되었지만 나중에 그의 알고리즘 은 1987 년 4 월 5 일까지의 날짜에 대해서만 작동 한다는 것을 깨달았 으며 1940 년대부터 여전히 제대로 변환되지 않은 날짜가 있습니다. 궁극적으로 UTCJava API를 사용하여 UTC현지 시간 으로 변환 한 타사 프로그램의 알고리즘과 정렬하려면 SQL Server 데이터베이스 의 날짜가 필요했습니다 .

등 내가 CLR예를 들어 위의 케빈 Feasel의 대답 거친 Chawla는의 예를 사용하여, 나는 또한 우리의 프론트 엔드가 할 자바를 사용하기 때문에, 자바를 사용하는 솔루션과 비교하고 싶습니다 UTC현지 시간으로 변환합니다.

Wikipedia는 1987 년 이전의 시간대 조정을 포함하는 8 가지 헌법 개정안을 언급하고 있으며, 그 중 다수는 다른 주에 매우 국한되어 있으므로 CLR과 Java가 다르게 해석 할 가능성이 있습니다. 프론트 엔드 애플리케이션 코드가 닷넷 또는 Java를 사용합니까, 아니면 1987 년 이전 날짜가 문제입니까?


2

CLR 저장 프로 시저를 사용하면이 작업을 쉽게 수행 할 수 있습니다.

[SqlFunction]
public static SqlDateTime ToLocalTime(SqlDateTime UtcTime, SqlString TimeZoneId)
{
    if (UtcTime.IsNull)
        return UtcTime;

    var timeZone = TimeZoneInfo.FindSystemTimeZoneById(TimeZoneId.Value);
    var localTime = TimeZoneInfo.ConvertTimeFromUtc(UtcTime.Value, timeZone);
    return new SqlDateTime(localTime);
}

사용 가능한 TimeZone을 테이블에 저장할 수 있습니다.

CREATE TABLE TimeZones
(
    TimeZoneId NVARCHAR(32) NOT NULL CONSTRAINT PK_TimeZones PRIMARY KEY,
    DisplayName NVARCHAR(64) NOT NULL,
    SupportsDaylightSavingTime BIT NOT NULL,
)

이 저장 프로시 저는 서버에서 가능한 표준 시간대로 테이블을 채 웁니다.

public partial class StoredProcedures
{
    [SqlProcedure]
    public static void PopulateTimezones()
    {
        using (var sql = new SqlConnection("Context Connection=True"))
        {
            sql.Open();

            using (var cmd = sql.CreateCommand())
            {
                cmd.CommandText = "DELETE FROM TimeZones";
                cmd.ExecuteNonQuery();

                cmd.CommandText = "INSERT INTO [dbo].[TimeZones]([TimeZoneId], [DisplayName], [SupportsDaylightSavingTime]) VALUES(@TimeZoneId, @DisplayName, @SupportsDaylightSavingTime);";
                var Id = cmd.Parameters.Add("@TimeZoneId", SqlDbType.NVarChar);
                var DisplayName = cmd.Parameters.Add("@DisplayName", SqlDbType.NVarChar);
                var SupportsDaylightSavingTime = cmd.Parameters.Add("@SupportsDaylightSavingTime", SqlDbType.Bit);

                foreach (var zone in TimeZoneInfo.GetSystemTimeZones())
                {
                    Id.Value = zone.Id;
                    DisplayName.Value = zone.DisplayName;
                    SupportsDaylightSavingTime.Value = zone.SupportsDaylightSavingTime;

                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

추가해야 할 때 CLR이 악해집니다 WITH PERMISSION_SET = UNSAFE. 일부 환경에서는 AWS RDS와 같이 허용하지 않습니다. 그리고 그것은 안전하지 않습니다. 안타깝게도 unsafe권한 없이 사용할 수있는 .Net 표준 시간대 구현은 없습니다. 여기여기를 참조 하십시오 .
Frédéric

2

SQL Server 버전 2016은이 문제를 한 번에 해결 합니다 . 이전 버전의 경우 CLR 솔루션이 가장 쉽습니다. 또는 특정 DST 규칙 (미국 만 해당)의 경우 T-SQL 함수는 비교적 간단 할 수 있습니다.

그러나 일반적인 T-SQL 솔루션이 가능하다고 생각합니다. 긴만큼 xp_regread작품이 시도 :

CREATE TABLE #tztable (Value varchar(50), Data binary(56));
DECLARE @tzname varchar(150) = 'SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE', @tzname, 'TimeZoneKeyName', @tzname OUT;
SELECT @tzname = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\' + @tzname
INSERT INTO #tztable
EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE', @tzname, 'TZI';
SELECT                                                                                  -- See http://msdn.microsoft.com/ms725481
 CAST(CAST(REVERSE(SUBSTRING(Data,  1, 4)) AS binary(4))      AS int) AS BiasMinutes,   -- UTC = local + bias: > 0 in US, < 0 in Europe!
 CAST(CAST(REVERSE(SUBSTRING(Data,  5, 4)) AS binary(4))      AS int) AS ExtraBias_Std, --   0 for most timezones
 CAST(CAST(REVERSE(SUBSTRING(Data,  9, 4)) AS binary(4))      AS int) AS ExtraBias_DST, -- -60 for most timezones: DST makes UTC 1 hour earlier
 -- When DST ends:
 CAST(CAST(REVERSE(SUBSTRING(Data, 13, 2)) AS binary(2)) AS smallint) AS StdYear,       -- 0 = yearly (else once)
 CAST(CAST(REVERSE(SUBSTRING(Data, 15, 2)) AS binary(2)) AS smallint) AS StdMonth,      -- 0 = no DST
 CAST(CAST(REVERSE(SUBSTRING(Data, 17, 2)) AS binary(2)) AS smallint) AS StdDayOfWeek,  -- 0 = Sunday to 6 = Saturday
 CAST(CAST(REVERSE(SUBSTRING(Data, 19, 2)) AS binary(2)) AS smallint) AS StdWeek,       -- 1 to 4, or 5 = last <DayOfWeek> of <Month>
 CAST(CAST(REVERSE(SUBSTRING(Data, 21, 2)) AS binary(2)) AS smallint) AS StdHour,       -- Local time
 CAST(CAST(REVERSE(SUBSTRING(Data, 23, 2)) AS binary(2)) AS smallint) AS StdMinute,
 CAST(CAST(REVERSE(SUBSTRING(Data, 25, 2)) AS binary(2)) AS smallint) AS StdSecond,
 CAST(CAST(REVERSE(SUBSTRING(Data, 27, 2)) AS binary(2)) AS smallint) AS StdMillisec,
 -- When DST starts:
 CAST(CAST(REVERSE(SUBSTRING(Data, 29, 2)) AS binary(2)) AS smallint) AS DSTYear,       -- See above
 CAST(CAST(REVERSE(SUBSTRING(Data, 31, 2)) AS binary(2)) AS smallint) AS DSTMonth,
 CAST(CAST(REVERSE(SUBSTRING(Data, 33, 2)) AS binary(2)) AS smallint) AS DSTDayOfWeek,
 CAST(CAST(REVERSE(SUBSTRING(Data, 35, 2)) AS binary(2)) AS smallint) AS DSTWeek,
 CAST(CAST(REVERSE(SUBSTRING(Data, 37, 2)) AS binary(2)) AS smallint) AS DSTHour,
 CAST(CAST(REVERSE(SUBSTRING(Data, 39, 2)) AS binary(2)) AS smallint) AS DSTMinute,
 CAST(CAST(REVERSE(SUBSTRING(Data, 41, 2)) AS binary(2)) AS smallint) AS DSTSecond,
 CAST(CAST(REVERSE(SUBSTRING(Data, 43, 2)) AS binary(2)) AS smallint) AS DSTMillisec
FROM #tztable;
DROP TABLE #tztable

(복잡한) T-SQL 함수는 이 데이터 를 사용 하여 현재 DST 규칙 동안 모든 날짜의 정확한 오프셋을 결정할 수 있습니다 .


2
DECLARE @TimeZone VARCHAR(50)
EXEC MASTER.dbo.xp_regread 'HKEY_LOCAL_MACHINE', 'SYSTEM\CurrentControlSet\Control\TimeZoneInformation', 'TimeZoneKeyName', @TimeZone OUT
SELECT @TimeZone
DECLARE @someUtcTime DATETIME
SET @someUtcTime = '2017-03-05 15:15:15'
DECLARE @TimeBiasAtSomeUtcTime INT
SELECT @TimeBiasAtSomeUtcTime = DATEDIFF(MINUTE, @someUtcTime, @someUtcTime AT TIME ZONE @TimeZone)
SELECT DATEADD(MINUTE, @TimeBiasAtSomeUtcTime * -1, @someUtcTime)

2
안녕 Joost! 게시 해 주셔서 감사합니다. 답변에 설명을 추가하면 이해하기 훨씬 쉬울 수 있습니다.
LowlyDBA

2

다음은 특정 영국 응용 프로그램을 위해 작성된 답변이며 순전히 SELECT를 기반으로합니다.

  1. 시간대 오프셋 없음 (예 : 영국)
  2. 3 월 마지막 일요일부터 10 월 마지막 일요일까지 일광 절약 시간 제로 작성 (영국 규칙)
  3. 일광 절약 시간 제가 시작되는 날 자정과 오전 1시 사이에는 적용되지 않습니다. 이 문제는 해결 될 수 있지만 작성된 응용 프로그램에는 필요하지 않습니다.

    -- A variable holding an example UTC datetime in the UK, try some different values:
    DECLARE
    @App_Date datetime;
    set @App_Date = '20250704 09:00:00'
    
    -- Outputting the local datetime in the UK, allowing for daylight saving:
    SELECT
    case
    when @App_Date >= dateadd(day, 1 - datepart(weekday, dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @App_Date), 0)))), dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @App_Date), 0))))
        and @App_Date < dateadd(day, 1 - datepart(weekday, dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @App_Date), 0)))), dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @App_Date), 0))))
        then DATEADD(hour, 1, @App_Date) 
    else @App_Date 
    end

짧은 이름 대신 긴 날짜 부분 이름을 사용하는 것이 좋습니다. 명확성을 위해. 참조 여러 "나쁜 습관"에 아론 버트 랜드의 우수한 기사
최대 버논

또한,에 오신 것을 환영합니다 데이터베이스 관리자 -하시기 바랍니다 여행을 당신이 이미하지 않은 경우!
Max Vernon

1
모두 감사하고 유용한 의견과 유용한 편집 제안을 드리겠습니다. 여기서는 완전히 초보자입니다. 어쨌든 나는 팹 :-) 인 1 점을 축적했습니다.
colinp_1

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