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


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が純粋な悪または怖いものではないことを人々に確信させることです...

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

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


時間の経過に伴う地域の変動の実際の複雑さを考えると、純粋なT-SQLでこれを試みることは「決定的に容易ではない」と言って、おそらくそれを誇張しています;-)。はい、そうです、SQLCLRはこの操作を実行する唯一の信頼できる効率的な手段です。そのために+1。参考:リンクされたブログの投稿は機能的には正しいが、ベストプラクティスに従っていないため、残念ながら非効率的です。UTCとサーバーのローカル時間を変換する関数は、SQL#ライブラリ(私は著者です)で使用できますが、無料版では使用できません
ソロモンラツキー

1
CLRは、追加する必要がある場合に悪になりますWITH PERMISSION_SET = UNSAFE。AWS RDSのように、一部の環境では許可されていません。そして、それはまあ、安全ではありません。残念ながら、unsafe許可なく使用できる.Netタイムゾーンの完全な実装はありません。こちらこちらをご覧ください。
フレデリック

15

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でも動作するはずです。質問がある場合は、教えてください。


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から1時間ずれていないタイムゾーンがあるため、このDATEPARTを使用すると問題が発生する場合があります。
マイケルグリーン14年

4
Michael Greenのコメントについては、SELECT DATEADD(MINUTE、DATEDIFF(MINUTE、GETUTCDATE()、GETDATE())、@utc)に変更することで問題に対処できます。
登録ユーザー

4
現在の時刻がDSTであるかどうかを判断し、DSTであるかどうかを比較するだけなので、これは機能しません。上記のサンプルコードと英国の日時を使用すると、現在は午前6時14分になっているはずですが、11月はDSTの外にあるため、GMTとUTCが一致するため午前5時14分になります。
マット

私は同意していますが、これは実際の質問には対応していませんが、この答えに関する限り、次の方が良いと思います:SELECT DATEADD(MINUTE、DATEPART(TZoffset、SYSDATETIMEOFFSET())、@utc)
Eamon

@Ludo Bernaerts:最初の使用ミリ秒、秒:UTCは今日オフセットので、これは仕事をしませんが、一定の時間(サマータイム-夏冬時間対)で、UTCオフセットとは異なる場合があります...
困惑

11

日時を正確に変換するように見えるユーザー定義関数を提供するStackOverflowでこの答えを見つけました

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

これは完璧ではなく、おそらく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


3

Stack Overflowでの同様の質問に対するいくつかの良い回答があります。Bob Albrightよる2番目の回答の T-SQLアプローチを使用して、データ変換コンサルタントによる混乱を解決しました。

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

以下のような私CLRの例上記のケビンFeaselの答えで厳しいチャウラの例を使用して、私も私たちのフロントエンドを行うために、Javaを使用しているため、Javaの使用するソリューションと比較したいUTC現地時間変換に。

ウィキペディアでは、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);
}

使用可能な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();
                }
            }
        }
    }
}

CLRは、追加する必要がある場合に悪になりますWITH PERMISSION_SET = UNSAFE。AWS RDSのように、一部の環境では許可されていません。そして、それはまあ、安全ではありません。残念ながら、unsafe許可なく使用できる.Netタイムゾーンの完全な実装はありません。こちらこちらをご覧ください。
フレデリック

2

SQL Serverバージョン2016は、この問題を完全に解決します。以前のバージョンでは、おそらくCLRソリューションが最も簡単です。または、特定のDSTルール(USのみなど)の場合、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. 夏時間の開始日の午前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

短い名前ではなく、長い日付部分の名前を使用することを検討することもできます。わかりやすくするために。アーロンバートランドのいくつかの「悪い習慣」に関する優れた記事を
マックスヴァーノン

また、データベース管理者へようこそ- まだお持ちでない場合は、ツアーに参加してください!
マックスヴァーノン

1
ありがとう、有益なコメントと有益な編集提案、私はここで完全に初心者です、どういうわけか私はfabである1ポイントを蓄積することができました:-)。
colinp_1

今、あなたは11を持っている
マックス・バーノン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.