SQL Server 2008 - 如何将GMT(UTC)日期时间转换为本地日期时间?

12

我有一个插入存储过程,其中传递了 GETDATE() 作为其中一个值,因为每个插入操作也会保存其插入时间。该存储过程托管在 SQL Azure 上,使用的是 GMT 时区。

现在,当我接收消息时,它们的时间戳列中都存储了 GMT 时间,我该如何将其转换为访问页面时所在地的本地 datetime

谢谢。


2
服务器时间有变动吗?你能检查一下吗? - Dustin Laine
1
@Slandau,当您说“在Azure中执行简单查询时它不能正常工作”时,您所指的“不能正常工作”是什么意思? - Abe Miessler
CURRENT_TIMESTAMP 返回相同的东西。 - slandau
@Mike M. 我该如何让它在每个人的本地时区中运行? - slandau
可能是重复的问题:TSQL:如何将本地时间转换为UTC?(SQL Server 2008) - Reinstate Monica Please
显示剩余10条评论
5个回答

10
您可以做如下操作:
declare @InputUtcDateTime datetime2 = '2011-05-20 06:30:18'

declare @LocalDateTime datetime2 = dateadd(minute, datepart(TZoffset, sysdatetimeoffset()), @InputUtcDateTime)
print @LocalDateTime
或者
declare @InputUtcDateTime datetime2 = '2011-05-20 06:30:18'

declare @LocalDateTime datetime2 = dateadd(minute, datediff(minute, sysutcdatetime(), sysdatetime()), @InputUtcDateTime)
print @LocalDateTime

30
这两种方法都是错误的。它们基于这样一个假设:今天本地时区的偏移量与输入的UTC日期所指的时间的本地时区偏移量相同。由于本地时区可能会受夏令时影响,因此其偏移量在过去可能与今天不同。 - Jerome Haltom
3
@wasabi +1 - 你关于这种方法的局限性是正确的。然而,我不同意它是错误的。这取决于你想要实现什么。 - Alex Aza
6
想要获取本地时区的正确时间,基于任意输入时间? - Jerome Haltom
为了处理夏令时转换,您需要像这样的查找表:http://www.codeproject.com/Articles/14769/SQL-Server-Convert-UTC-to-Local-Time - hross
1
正如@wasabi所解释的那样,这种方法行不通。它假设服务器的本地时间始终与GMT/UTC有相同的偏移量 - 即使对于格林威治来说也不是这样,因为存在夏令时! - decates
显示剩余2条评论

6
除了夏令时问题外,为什么不采用以下简化方式:
```html

除了夏令时问题外,为什么不采用以下简化方式:

```
yourDateTime - getutcdate() + getdate()

1
CREATE FUNCTION [dbo].[fn_DateTime_GMTFromLocal](@LocalTime DATETIME)
RETURNS DATETIME2(3)
AS
BEGIN
/*
=============================================
 Author:        Mark Griffiths
 Create date:   29/05/2018
 Description:   BST runs from 02:00AM on the last Sunday of March to the same time on the last Sunday of October.       
    The Series of DATEDIFFs and DATEADDS below function as follows
    1   ●   Count the number of months there have been between the given date and start of computer time
    2   ●   Add that number of months to the end of the first month to get the end of the given month
    3   ●   Count the number of days there have been between the end of the given month and the first Saturday
    4   ●   Add that number of days to the calculated end of the given month
    5   ●   Add Two hours to that time as the clocks go back at 02:00 in the morning

    I know that the tabbing below makes it all look odd, but the description above is the best way I could find to comment things, given the nesting...
    The comments in the code below should help find the nesting levels and the numbers refer to the bullet points above.
=============================================
-- Test Variables --
DECLARE @GMTime DATETIME2(3) = '2018-05-01 12:00:00.000'
*/

    DECLARE @RealTime As DATETIME2(3)
    DECLARE @Year VARCHAR(4)
    SET @Year = CONVERT(VARCHAR,DATEPART(YEAR,@GMTime))
    DECLARE @StartOfBST AS DATETIME
    DECLARE @EndOfBST AS DATETIME
    SELECT
        @StartOfBST =
        DATEADD     -----------------------------------------------------------------------------------------
            (                                                       --                                      |
             HOUR                                                   --                                      |
            ,2                                                      --                                      |
            ,DATEADD    -----------------------------------------------------------------------------       |
                (                                                   --                              |       |
                 DAY                                                --                              |       |
                ,DATEDIFF       -------------------------------------------------------------       |       |
                    (                                               --                      |       |       |
                     DAY                                            --                      |       |       |
                    ,'19000107'                                     --                      |       |       5
                    ,DATEADD            ---------------------------------------------       |       |       |
                        (                                           --              |       3       4       |
                         MONTH                                      --              |       |       |       |
                        ,DATEDIFF(MONTH,0,CONVERT(DATE,'03/01/' + @Year)) -- 1      2       |       |       |
                        ,CONVERT(DATE,'01/31/1900')                 --              |       |       |       |
                        )               ---------------------------------------------       |       |       |
                    )/7*7       -------------------------------------------------------------       |       |
                    ,'19000107'                                     --                              |       |
                )               ---------------------------------------------------------------------       |
            ),      -----------------------------------------------------------------------------------------
    @EndOfBST =
        DATEADD(HOUR,2,DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,CONVERT(DATE,'10/01/' + @Year)),30))/7*7,'19000107'))
    SET @RealTime = CASE
                    WHEN @GMTime BETWEEN @StartOfBST AND @EndOfBST THEN DATEADD(HOUR,-1,@GMTime)
                    ELSE @GMTime
                END
RETURN @RealTime;
--SELECT @RealTime
END

*编辑:将CONVERT(DATE,'01/30/1900')更改为CONVERT(DATE,'01/31/1900'),因为自公元前45年以来,1月份有31天。这会导致2019年和其他一些3月最后一个星期日是31号的日期计算出现错误。


1
这个代码完美运行,尽管我不得不在结尾将-1更改为+1,以将UTC时间更改为GMT。 - Lewis Williams
是的,你可以像那样走两种不同的方式 - 我写了两个函数,每个函数都对应一种方式,并在我的脚本中单独引用它们 - 它们只有 +-1 不同。顺便说一下,让我惊奇的是被接受的答案(并且还被点赞)实际上是不起作用的! - High Plains Grifter

1
以MST为例...考虑到每个DTM已经存储在GMT中,这简化了事情...
SWITCHOFFSET(CONVERT(DATETIMEOFFSET, [ColumnName]), '-07:00')

现在,如果您的本地日期/时间与GMT/UTC不同,您可能想使用以下内容...

SWITCHOFFSET(TODATETIMEOFFSET([ColumnName], datepart(tz,sysdatetimeoffset())),'+00:00')

以下是细节说明。

  • SWITCHOFFSET - 将DateTimeOffset值转换为不同的时区,同时保留偏移量。
  • TODATETIMEOFFSET - 将DateTime值转换为指定时区的DateTimeOffset值。
  • DATEPART - 在这种情况下,获取本地日期时间的时区部分。
  • '+00:00' - 目标偏移量,在第二个示例中是UTC/GMT目标,从本地...前一个示例是到MST。

注意/警告:我认为这并没有考虑夏令时,这可能会对您造成影响。如果绝对保留不是必要的,您可以添加一个辅助列,进行粗略转换并安全地继续。

您可能希望将逻辑抽象成函数调用,以便考虑DST的保留...尽管这不应该过于困难。


0

这是一个处理历史数据的函数。我为英国夏令时编写了它,不幸的是英国夏令时发生在三月和十月的最后一个星期日,使得逻辑有点复杂。

基本上硬编码的日期部分01/03是在寻找三月的最后一个星期日,01/10是在寻找十月的最后一个星期日(这是当地时钟向前或向后调整的时间)。注意:如果您的服务器使用本地美国日期,请将这两个日期部分反转为03/01和10/01!!!

因此,您可以输入一个UTC日期,它会自动计算出历史日期是BST还是GMT。虽然不是在大数据集上使用的最佳方案,但它是一种解决方案。

运行此脚本以创建函数并在您的选择中内联调用它。SQL 2008存在用户定义函数的问题,似乎会在代码下面放置一个红线,但只要您使用dbo前缀(SELECT dbo.UTCConvert(yourdate)来运行它),它仍然会运行。

CREATE FUNCTION [dbo].[UTCConvert] 
(

    @p1 datetime
)
RETURNS datetime
AS
BEGIN

    DECLARE @Result datetime


RETURN CASE 
WHEN
@p1 >
(DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,'01/03/' + CAST(DATEPART(year,@p1) as CHAR)),30))/7*7,'19000107'))
AND
@p1<
(DATEADD(day,DATEDIFF(day,'19000107',DATEADD(month,DATEDIFF(MONTH,0,'01/10/' + CAST(DATEPART(year,@p1) as CHAR)),30))/7*7,'19000107'))
THEN (DATEADD(HH, 1, @p1)) 
ELSE @p1
END
END

如果您的服务器使用本地美国日期,因为它位于美国,那么您很可能会遇到更严重的问题。如果您正在查看此问题,那么很有可能您正在存储至少一个应用夏令时的位置的日期。这意味着您至少要担心2007年转换日期的变化。您可能需要担心多个时区,可能是亚利桑那州,甚至是纳瓦霍族。您真的希望在SQL Server中存储UTC或datetimeoffset,并在业务层中进行日期计算。 - MattW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接