web-dev-qa-db-ja.com

夏時間の前または後の日付のUTCと現地時間の正しいオフセットを取得するにはどうすればよいですか?

私は現在、以下を使用してUTC日時からローカル日時を取得しています。

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

私の問題は、夏時間がGetUTCDate()と_@utcDateTime_の間に発生すると、_@localDateTime_が1時間ずれてしまうことです。

現在の日付ではない日付のutcから現地時間に変換する簡単な方法はありますか?

SQL Server 2005を使用しています

30
Rachel

現在でないUTC日付を現地時間に変換する最良の方法は、CLRを使用することです。コード自体は簡単です。難しい部分は通常、CLRが純粋な悪でも怖いものでもないことを人々に納得させることです...

多くの例の1つについては、 Harsh Chawlaのトピックに関するブログ投稿 をチェックしてください。

残念ながら、CLRベースのソリューションを除いて、このタイプの変換を処理できる組み込み機能はありません。このような処理を行うT-SQL関数を作成することもできますが、その場合は日付変更ロジックを自分で実装する必要があります。

19
Kevin Feasel

Microsoft SQL Serverで日時とタイムゾーンの処理に苦労している人を助けるために、codeplexで 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でも動作するはずです。ご不明な点がございましたら、お気軽にお問い合わせください。

15
adss

私は常にこのTSQLコマンドを使用します。

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

-- the local time

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

それは非常にシンプルで、仕事をします。

13
Ludo Bernaerts

日時を正確に翻訳しているように見えるユーザー定義関数を提供するStackOverflowで this answer を見つけました

変更する必要があるのは、上部の@offset変数だけで、この関数を実行しているSQLサーバーのタイムゾーンオフセットに設定する必要があります。私の場合、SQLサーバーはGMT-5であるESTを使用しています

これは完璧ではなく、30分または15分のTZオフセットがあるような多くの場合には機能しないでしょう(私は Kevin推奨 のような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
11
Rachel

SQL Server 2016以降の場合、 AT TIME ZONE を使用できます。 自動的に処理 日の光の節約時間になります。

5
gotqn

Stack Overflowで尋ねられた 同様の質問 には2つの良い答えがあります。 Bob Albrightによる2番目の回答 からT-SQLアプローチを使用して、データ変換コンサルタントによって引き起こされた混乱を解消しました。

ほとんどすべてのデータで機能しましたが、彼のアルゴリズムは1987年4月5日までの日付でしか機能しないことに気付きました。 1940年代の日付がいくつかありましたが、まだ正しく変換されていませんでした。最終的に、Java APIを使用してUTCから現地時間に変換するサードパーティプログラムのアルゴリズムに合わせるために、SQL ServerデータベースのUTC日付が必要でした。

私はCLRの例が好きです Kevin Feaselの上記の回答の場合 Harsh Chawlaの例を使用し、Javaを使用するソリューションと比較したいと思います。フロントエンドがJavaを使用して、UTCを現地時間に変換します。

ウィキペディアは、1987年より前のタイムゾーン調整を含む8つの異なる憲法改正に言及しており、それらの多くは異なる州に非常にローカライズされているため、CLRとJavaが異なる解釈をする可能性があります。フロントエンドアプリケーションコードはドットネットまたはJavaを使用していますか、それとも1987年より前の日付が問題ですか?

3
kkarns
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

これは、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);
}

使用可能なTimeZonesをテーブルに格納できます。

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();
                }
            }
        }
    }
}
2
Tim Cooke

これは、特定の英国のアプリケーション向けに書かれた回答であり、純粋にSELECTに基づいています。

  1. タイムゾーンオフセットなし(例:英国)
  2. 3月の最後の日曜日に始まり、10月の最後の日曜日に終わる夏時間について書かれている(英国の規則)
  3. 夏時間が始まる日の午前0時から午前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
    
2
colinp_1

SQL Serverバージョン2016はこの問題を解決します 1回限り、すべての場合 。以前のバージョンでは、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関数は this data を使用して、現在のDSTルール中のすべての日付の正確なオフセットを決定できます。

2

Colinp_1に基づいて post 日時をDSTおよびTZを考慮に入れるdatetimeoffsetに変換するソリューションを作成しました。お役に立てれば!

DECLARE @offset int -- offset in min
DECLARE @dst bit
DECLARE @appDate datetime

set @dst = 1
set @offset = +60
set @appDate = '2017-04-06 14:21:10.000'

-- output the start and end datetime of DST to the given @appDate
select dateadd(hour, 2, 
                  dateadd(day, 1 - datepart(weekday
                                 , dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @appDate), 0))))
                                 , dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @appDate), 0))))) AS 'MEZ -> MESZ'
     , dateadd(hour, 2, 
                  dateadd(day, 1 - datepart(weekday
                                 , dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @appDate), 0))))
                                 , dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @appDate), 0))))) AS 'MESZ -> MEZ'

-- output the @appDate as datetimeoffset including offset and DST
SELECT @dst AS 'DST on'
     , @offset AS 'TZ offset'
     , @appDate AS 'originalDate'
     , qDT.isAppDateInDST
     , qDT.datetimeoffset
     , CONVERT(datetime, qDT.datetimeoffset, 1) AS 'UTC'
FROM (
    SELECT 
        CASE WHEN @dst = 1 THEN -- check if DST is needed
           CASE
                WHEN qDST.isAppDateInDST = 1
                THEN TODATETIMEOFFSET(@appDate, @offset + 60) -- add 1 hour to @appDate when its in DST and convert to DATETIMEOFFSET
                ELSE TODATETIMEOFFSET(@appDate, @offset) -- convert to     DATETIMEOFFSET with given offset
        END
    ELSE 
        TODATETIMEOFFSET(@appDate, @offset) -- convert to DATETIMEOFFSET with given offset
        END AS 'datetimeoffset'
      , qDST.isAppDateInDST
    FROM (
        SELECT 
            CASE WHEN @appDate >= dateadd(hour, 2, 
                                    dateadd(day, 1 - datepart(weekday
                                                    , dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @appDate), 0))))
                                                    , dateadd(day, -1, dateadd(month, 3, dateadd(year, datediff(year, 0, @appDate), 0)))))
                    and @appDate < dateadd(hour, 2, 
                                    dateadd(day, 1 - datepart(weekday
                                                    , dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @appDate), 0))))
                                                    , dateadd(day, -1, dateadd(month, 10, dateadd(year, datediff(year, 0, @appDate), 0)))))
                THEN 1
            ELSE 0
            END AS 'isAppDateInDST'
    ) qDST
) qDT

GO
0
Mike