私は現在、以下を使用してUTC日時からローカル日時を取得しています。
_SET @offset = DateDiff(minute, GetUTCDate(), GetDate())
SET @localDateTime = DateAdd(minute, @offset, @utcDateTime)
_
私の問題は、夏時間がGetUTCDate()
と_@utcDateTime
_の間に発生すると、_@localDateTime
_が1時間ずれてしまうことです。
現在の日付ではない日付のutcから現地時間に変換する簡単な方法はありますか?
SQL Server 2005を使用しています
現在でないUTC日付を現地時間に変換する最良の方法は、CLRを使用することです。コード自体は簡単です。難しい部分は通常、CLRが純粋な悪でも怖いものでもないことを人々に納得させることです...
多くの例の1つについては、 Harsh Chawlaのトピックに関するブログ投稿 をチェックしてください。
残念ながら、CLRベースのソリューションを除いて、このタイプの変換を処理できる組み込み機能はありません。このような処理を行うT-SQL関数を作成することもできますが、その場合は日付変更ロジックを自分で実装する必要があります。
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でも動作するはずです。ご不明な点がございましたら、お気軽にお問い合わせください。
私は常にこのTSQLコマンドを使用します。
-- the utc value
declare @utc datetime = '20/11/2014 05:14'
-- the local time
select DATEADD(hh, DATEDIFF(hh, getutcdate(), getdate()), @utc)
それは非常にシンプルで、仕事をします。
日時を正確に翻訳しているように見えるユーザー定義関数を提供する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
SQL Server 2016以降の場合、 AT TIME ZONE を使用できます。 自動的に処理 日の光の節約時間になります。
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年より前の日付が問題ですか?
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)
これは、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();
}
}
}
}
}
これは、特定の英国のアプリケーション向けに書かれた回答であり、純粋にSELECTに基づいています。
夏時間が始まる日の午前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
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ルール中のすべての日付の正確なオフセットを決定できます。
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