Преобразовать столбец Datetime из UTC в местное время в операторе выбора - PullRequest
173 голосов
/ 07 ноября 2011

Я делаю несколько запросов на выборку SQL и хотел бы преобразовать свой столбец даты и времени UTC в местное время, чтобы оно отображалось как местное время в результатах моего запроса. Обратите внимание, я не собираюсь делать это преобразование с помощью кода, а скорее, когда я выполняю ручные и случайные запросы SQL к моим базам данных.

Ответы [ 23 ]

282 голосов
/ 07 ноября 2011

Вы можете сделать это следующим образом на SQL Server 2008 или более поздней версии:

SELECT CONVERT(datetime, 
               SWITCHOFFSET(CONVERT(datetimeoffset, 
                                    MyTable.UtcColumn), 
                            DATENAME(TzOffset, SYSDATETIMEOFFSET()))) 
       AS ColumnInLocalTime
FROM MyTable

Вы также можете сделать менее многословным:

SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), MyTable.UtcColumn) 
       AS ColumnInLocalTime
FROM MyTable

Что бы вы ни делали, не не используйте - для вычитания дат, потому что операция не является атомарной, и вы будете иногда получать неопределенные результаты из-за условий гонки между системным временем и местным временем проверяется в разное время (т. е. неатомно).

Обратите внимание, что этот ответ не учитывает DST. Если вы хотите включить корректировку DST, см. Также следующий вопрос SO:

Как создать функции начала и окончания перехода на летнее время в SQL Server

33 голосов
/ 16 июня 2017

Я не нашел ни одного из этих примеров полезным для получения даты и времени, сохраненных как UTC, с датой и временем в указанном часовом поясе (НЕ часовом поясе сервера, поскольку базы данных SQL Azure работают как UTC). Вот как я справился с этим. Это не элегантно, но просто и дает правильный ответ без ведения других таблиц:

select CONVERT(datetime, SWITCHOFFSET(dateTimeField, DATEPART(TZOFFSET, 
dateTimeField AT TIME ZONE 'Eastern Standard Time')))
20 голосов
/ 23 октября 2013

Если вам нужно преобразование, отличное от местоположения вашего сервера, вот функция, которая позволяет вам передавать стандартное смещение и счета для летнего времени США:

-- =============================================
-- Author:      Ron Smith
-- Create date: 2013-10-23
-- Description: Converts UTC to DST
--              based on passed Standard offset
-- =============================================
CREATE FUNCTION [dbo].[fn_UTC_to_DST]
(
    @UTC datetime,
    @StandardOffset int
)
RETURNS datetime
AS
BEGIN

    declare 
        @DST datetime,
        @SSM datetime, -- Second Sunday in March
        @FSN datetime  -- First Sunday in November

    -- get DST Range
    set @SSM = datename(year,@UTC) + '0314' 
    set @SSM = dateadd(hour,2,dateadd(day,datepart(dw,@SSM)*-1+1,@SSM))
    set @FSN = datename(year,@UTC) + '1107'
    set @FSN = dateadd(second,-1,dateadd(hour,2,dateadd(day,datepart(dw,@FSN)*-1+1,@FSN)))

    -- add an hour to @StandardOffset if @UTC is in DST range
    if @UTC between @SSM and @FSN
        set @StandardOffset = @StandardOffset + 1

    -- convert to DST
    set @DST = dateadd(hour,@StandardOffset,@UTC)

    -- return converted datetime
    return @DST

END

GO
6 голосов
/ 08 февраля 2017

Использование новых возможностей SQL Server 2016:

CREATE FUNCTION ToLocalTime(@dtUtc datetime, @timezoneId nvarchar(256))
RETURNS datetime
AS BEGIN

return @dtUtc AT TIME ZONE 'UTC' AT TIME ZONE @timezoneId

/* -- second way, faster

return SWITCHOFFSET(@dtUtc , DATENAME(tz, @dtUtc AT TIME ZONE @timezoneId))

*/

/* -- third way

declare @dtLocal datetimeoffset
set @dtLocal = @dtUtc AT TIME ZONE @timezoneId
return dateadd(minute, DATEPART (TZoffset, @dtLocal), @dtUtc)

*/

END
GO

Но процедура clr работает в 5 раз быстрее: '- (

) Обратите внимание, что смещение для одной TimeZone может измениться на зиму или летовремя, например

select cast('2017-02-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'
select cast('2017-08-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'

результаты:

2017-02-08 09:00:00.000 -05:00
2017-08-08 09:00:00.000 -04:00

Нельзя просто добавить постоянное смещение.

4 голосов
/ 25 апреля 2015

Если опция CLR для вашей базы данных является опцией, а также используется часовой пояс сервера SQL, ее можно легко записать в .Net.

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction]
    public static SqlDateTime fn_GetLocalFromUTC(SqlDateTime UTC)
    {
        if (UTC.IsNull)
            return UTC;

        return new SqlDateTime(UTC.Value.ToLocalTime());
    }
}

Входит значение даты и времени UTC и локальное времязначение относительно сервера выходит.Нулевые значения возвращают ноль.

4 голосов
/ 31 января 2016

Нет простого способа сделать это правильным и общим способом.

Прежде всего следует понимать, что смещение зависит от рассматриваемой даты, часового пояса и летнего времени.GetDate()-GetUTCDate только дает вам смещение сегодня в TZ сервера, которое не имеет значения.

Я видел только два рабочих решения и у меня много поиска.

1) Пользовательский SQLработать с парой таблиц базовых данных, таких как часовые пояса и правила летнего времени на TZ.Работает, но не очень элегантно.Я не могу опубликовать его, так как у меня нет кода.

РЕДАКТИРОВАТЬ: Вот пример этого метода https://gist.github.com/drumsta/16b79cee6bc195cd89c8

2) Добавить сборку .net в БД,.Net может сделать это очень легко.Это работает очень хорошо, но недостатком является то, что вам нужно настроить несколько параметров на уровне сервера, и конфигурация легко нарушается, например, если вы восстанавливаете базу данных.Я использую этот метод, но не могу опубликовать его, так как у меня нет кода.

3 голосов
/ 27 марта 2014

Я обнаружил, что одноразовая функция работает слишком медленно при большом количестве данных. Поэтому я сделал это, присоединившись к табличной функции, которая позволила бы рассчитать разницу в часах. В основном это сегменты даты и времени со смещением часа. Год будет 4 ряда. Так что табличная функция

dbo.fn_getTimeZoneOffsets('3/1/2007 7:00am', '11/5/2007 9:00am', 'EPT')

вернет эту таблицу:

startTime          endTime   offset  isHr2
3/1/07 7:00     3/11/07 6:59    -5    0
3/11/07 7:00    11/4/07 6:59    -4    0
11/4/07 7:00    11/4/07 7:59    -5    1
11/4/07 8:00    11/5/07 9:00    -5    0

Это учитывает переход на летнее время. Пример его использования приведен ниже, а полное сообщение в блоге: здесь .

select mt.startTime as startUTC, 
    dateadd(hh, tzStart.offset, mt.startTime) as startLocal, 
    tzStart.isHr2
from MyTable mt 
inner join dbo.fn_getTimeZoneOffsets(@startViewUTC, @endViewUTC, @timeZone)  tzStart
on mt.startTime between tzStart.startTime and tzStart.endTime
3 голосов
/ 27 марта 2014

Вот версия, которая учитывает переход на летнее время, смещение UTC и не привязана к определенному году.

---------------------------------------------------------------------------------------------------
--Name:     udfToLocalTime.sql
--Purpose:  To convert UTC to local US time accounting for DST
--Author:   Patrick Slesicki
--Date:     3/25/2014
--Notes:    Works on SQL Server 2008R2 and later, maybe SQL Server 2008 as well.
--          Good only for US States observing the Energy Policy Act of 2005.
--          Function doesn't apply for years prior to 2007.
--          Function assumes that the 1st day of the week is Sunday.
--Tests:        
--          SELECT dbo.udfToLocalTime('2014-03-09 9:00', DEFAULT)
--          SELECT dbo.udfToLocalTime('2014-03-09 10:00', DEFAULT)
--          SELECT dbo.udfToLocalTime('2014-11-02 8:00', DEFAULT)
--          SELECT dbo.udfToLocalTime('2014-11-02 9:00', DEFAULT)
---------------------------------------------------------------------------------------------------
ALTER FUNCTION udfToLocalTime
    (
    @UtcDateTime    AS DATETIME
    ,@UtcOffset     AS INT = -8 --PST
    )
RETURNS DATETIME
AS 
BEGIN
    DECLARE 
        @PstDateTime    AS DATETIME
        ,@Year          AS CHAR(4)
        ,@DstStart      AS DATETIME
        ,@DstEnd        AS DATETIME
        ,@Mar1          AS DATETIME
        ,@Nov1          AS DATETIME
        ,@MarTime       AS TIME
        ,@NovTime       AS TIME
        ,@Mar1Day       AS INT
        ,@Nov1Day       AS INT
        ,@MarDiff       AS INT
        ,@NovDiff       AS INT

    SELECT
        @Year       = YEAR(@UtcDateTime)
        ,@MarTime   = CONVERT(TIME, DATEADD(HOUR, -@UtcOffset, '1900-01-01 02:00'))
        ,@NovTime   = CONVERT(TIME, DATEADD(HOUR, -@UtcOffset - 1, '1900-01-01 02:00'))
        ,@Mar1      = CONVERT(CHAR(16), @Year + '-03-01 ' + CONVERT(CHAR(5), @MarTime), 126)
        ,@Nov1      = CONVERT(CHAR(16), @Year + '-11-01 ' + CONVERT(CHAR(5), @NovTime), 126)
        ,@Mar1Day   = DATEPART(WEEKDAY, @Mar1)
        ,@Nov1Day   = DATEPART(WEEKDAY, @Nov1)

    --Get number of days between Mar 1 and DST start date
    IF @Mar1Day = 1 SET @MarDiff = 7
    ELSE SET @MarDiff = 15 - @Mar1Day

    --Get number of days between Nov 1 and DST end date
    IF @Nov1Day = 1 SET @NovDiff = 0
    ELSE SET @NovDiff = 8 - @Nov1Day

    --Get DST start and end dates
    SELECT 
        @DstStart   = DATEADD(DAY, @MarDiff, @Mar1)
        ,@DstEnd    = DATEADD(DAY, @NovDiff, @Nov1)

    --Change UTC offset if @UtcDateTime is in DST Range
    IF @UtcDateTime >= @DstStart AND @UtcDateTime < @DstEnd SET @UtcOffset = @UtcOffset + 1

    --Get Conversion
    SET @PstDateTime = DATEADD(HOUR, @UtcOffset, @UtcDateTime)
    RETURN @PstDateTime
END
GO
2 голосов
/ 16 февраля 2017
 declare @mydate2 datetime
 set @mydate2=Getdate()
 select @mydate2 as mydate,
 dateadd(minute, datediff(minute,getdate(),@mydate2),getutcdate())
2 голосов
/ 16 августа 2017

Ни один из них не работал для меня, но это ниже работало на 100%. Надеюсь, что это может помочь другим, пытающимся преобразовать это, как я.

CREATE FUNCTION [dbo].[fn_UTC_to_EST]
(
    @UTC datetime,
    @StandardOffset int
)
RETURNS datetime
AS
BEGIN

declare 
    @DST datetime,
    @SSM datetime, -- Second Sunday in March
    @FSN datetime  -- First Sunday in November
-- get DST Range
set @SSM = DATEADD(dd,7 + (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))+'02:00:00' 
set @FSN = DATEADD(dd, (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0)) +'02:00:00'

-- add an hour to @StandardOffset if @UTC is in DST range
if @UTC between @SSM and @FSN
    set @StandardOffset = @StandardOffset + 1

-- convert to DST
set @DST = dateadd(hour,@StandardOffset,@UTC)

-- return converted datetime
return @DST

END
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...