it-swarm.com.de

Wie kann ich den korrekten Versatz zwischen UTC und Ortszeit für ein Datum vor oder nach der Sommerzeit ermitteln?

Ich verwende derzeit Folgendes, um eine lokale Datumszeit von einer UTC-Datumszeit zu erhalten:

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

Mein Problem ist, dass, wenn zwischen GetUTCDate() und @utcDateTime Sommerzeit auftritt, der @localDateTime Eine Stunde frei hat.

Gibt es eine einfache Möglichkeit, für ein Datum, das nicht das aktuelle Datum ist, von utc in die Ortszeit umzurechnen?

Ich verwende SQL Server 2005

30
Rachel

Der beste Weg, ein nicht aktuelles UTC-Datum in die Ortszeit umzuwandeln, ist die Verwendung der CLR. Der Code selbst ist einfach; Der schwierige Teil ist normalerweise, die Leute davon zu überzeugen, dass die CLR nicht rein böse oder beängstigend ist ...

Eines der vielen Beispiele finden Sie unter Harsh Chawlas Blog-Beitrag zum Thema .

Leider gibt es nichts Eingebautes, das diese Art der Konvertierung verarbeiten kann, außer für CLR-basierte Lösungen. Sie könnten eine T-SQL-Funktion schreiben, die so etwas tut, aber dann müssten Sie die Datumsänderungslogik selbst implementieren, und ich würde das entschieden nicht einfach nennen.

19
Kevin Feasel

Ich habe das Projekt T-SQL Toolbox auf Codeplex entwickelt und veröffentlicht, um allen zu helfen, die mit der Behandlung von Datum und Uhrzeit in Microsoft SQL Server zu kämpfen haben. Es ist Open Source und völlig kostenlos zu benutzen.

Es bietet einfache UDFs zur Konvertierung von Datum und Uhrzeit mit einfachem T-SQL (keine CLRs) sowie vorab ausgefüllte Konfigurationstabellen. Und es bietet volle DST-Unterstützung (Sommerzeit).

Eine Liste aller unterstützten Zeitzonen finden Sie in der Tabelle "DateTimeUtil.Timezone" (in der T-SQL Toolbox-Datenbank enthalten).

In Ihrem Beispiel können Sie das folgende Beispiel verwenden:

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
)

Dies gibt den konvertierten lokalen Datum/Uhrzeit-Wert zurück.

Leider wird es für SQL Server 2008 oder höher nur aufgrund neuerer Datentypen (DATE, TIME, DATETIME2) unterstützt. Da jedoch der vollständige Quellcode bereitgestellt wird, können Sie die Tabellen und UDFs einfach anpassen, indem Sie sie durch DATETIME ersetzen. Ich habe kein MSSQL 2005 zum Testen verfügbar, aber es sollte dann auch mit MSSQL 2005 funktionieren. Bei Fragen lassen Sie es mich einfach wissen.

15
adss

Ich benutze immer diesen TSQL-Befehl.

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

-- the local time

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

Es ist sehr einfach und macht den Job.

13
Ludo Bernaerts

Ich habe diese Antwort auf StackOverflow gefunden, das eine benutzerdefinierte Funktion bietet, die die Datenzeiten genau zu übersetzen scheint

Das einzige, was Sie ändern müssen, ist das @offset Variable oben, um den Zeitzonenversatz des SQL-Servers festzulegen, auf dem diese Funktion ausgeführt wird. In meinem Fall verwendet unser SQL Server EST, GMT-5

Es ist nicht perfekt und wird wahrscheinlich in vielen Fällen nicht funktionieren. Es hat TZ-Offsets von einer halben Stunde oder 15 Minuten (für diejenigen, die ich eine CLR-Funktion wie Kevin empfohlen empfehlen würde), aber es funktioniert gut genug für die meisten generischen Zeitzonen in Nordamerika.

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

Für SQL Server 2016+ können Sie AT TIME ZONE verwenden. Es wird automatisch behandelt die Sommerzeit.

5
gotqn

Es gibt ein paar gute Antworten auf eine ähnliche Frage , die bei Stack Overflow gestellt wurde. Ich habe einen T-SQL-Ansatz aus dem zweite Antwort von Bob Albright verwendet, um ein Durcheinander zu beseitigen, das von einem Datenkonvertierungsberater verursacht wurde.

Es funktionierte für fast alle unsere Daten, aber dann wurde mir später klar, dass sein Algorithmus nur für Daten funktioniert, die bis zum 5. April 1987 zurückreichen , und wir hatte einige Daten aus den 1940er Jahren, die immer noch nicht richtig konvertierten. Letztendlich brauchten wir die Daten UTC in unserer SQL Server-Datenbank, um mit einem Algorithmus in einem Programm eines Drittanbieters übereinzustimmen, der die API Java verwendete, um von UTC in die Ortszeit zu konvertieren .

Ich mag das Beispiel CLRin Kevin Feasels Antwort oben unter Verwendung des Beispiels von Harsh Chawla, und ich möchte es auch mit einer Lösung vergleichen, die Java verwendet, da unser Frontend Java, um die Konvertierung von UTC in lokale Zeit durchzuführen.

Wikipedia erwähnt 8 verschiedene Verfassungsänderungen, die Zeitzonenanpassungen vor 1987 beinhalten, und viele davon sind sehr lokal in verschiedenen Staaten lokalisiert, so dass die Möglichkeit besteht, dass die CLR und Java sie unterschiedlich interpretieren. Verwendet Ihr Front-End-Anwendungscode Dotnet oder Java oder sind Daten vor 1987 ein Problem für Sie?

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

Sie können dies einfach mit einer gespeicherten CLR-Prozedur tun.

[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);
}

Sie können die verfügbaren Zeitzonen in einer Tabelle speichern:

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

Und diese gespeicherte Prozedur füllt die Tabelle mit den möglichen Zeitzonen auf Ihrem Server.

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

Hier ist eine Antwort, die für eine bestimmte britische Anwendung geschrieben wurde und ausschließlich auf SELECT basiert.

  1. Kein Zeitzonenversatz (z. B. Großbritannien)
  2. Geschrieben für die Sommerzeit, beginnend am letzten Sonntag im März und endend am letzten Sonntag im Oktober (britische Regeln)
  3. Gilt nicht zwischen Mitternacht und 1 Uhr morgens, wenn die Sommerzeit beginnt. Dies könnte korrigiert werden, aber die Anwendung, für die es geschrieben wurde, erfordert es nicht.

    -- 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 Version 2016 wird dieses Problem lösen ein für alle Mal . Für frühere Versionen ist eine CLR-Lösung wahrscheinlich am einfachsten. Oder für eine bestimmte Sommerzeitregel (wie nur in den USA) kann eine T-SQL-Funktion relativ einfach sein.

Ich denke jedoch, dass eine generische T-SQL-Lösung möglich sein könnte. So lange wie xp_regread funktioniert, versuchen Sie dies:

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

Eine (komplexe) T-SQL-Funktion könnte diese Daten verwenden, um den genauen Versatz für alle Daten während der aktuellen Sommerzeitregel zu bestimmen.

2

Basierend auf Colinp_1 post habe ich eine Lösung zum Konvertieren einer datetime in datetimeoffset erstellt, die DST und TZ berücksichtigt. Hoffe das hilft!

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