如何将datetime转换为datetimeoffset?

Ian*_*oyd 36 sql-server internationalization sql-server-2008-r2

如何将SQL Server datetime值转换为datetimeoffset值?


例如,现有表包含datetime全部处于"本地"服务器时间的值.

SELECT TOP 5 ChangeDate FROM AuditLog

ChangeDate
=========================
2013-07-25 04:00:03.060
2013-07-24 04:00:03.073
2013-07-23 04:00:03.273
2013-07-20 04:00:02.870
2013-07-19 04:00:03.780
Run Code Online (Sandbox Code Playgroud)

我的服务器(恰好)(现在,今天)比UTC晚了四个小时(现在,在美国东部时区,夏令时处于活动状态):

SELECT SYSDATETIMEOFFSET()

2013-07-25 14:42:41.6450840 -04:00
Run Code Online (Sandbox Code Playgroud)

我想将存储的datetime值转换为datetimeoffset值; 使用服务器的当前时区偏移信息.

想要的价值观是:

ChangeDate               ChangeDateOffset
=======================  ==================================
2013-07-25 04:00:03.060  2013-07-25 04:00:03.0600000 -04:00
2013-07-24 04:00:03.073  2013-07-24 04:00:03.0730000 -04:00
2013-07-23 04:00:03.273  2013-07-23 04:00:03.2730000 -04:00
2013-07-20 04:00:02.870  2013-07-20 04:00:02.8700000 -04:00
2013-07-19 04:00:03.780  2013-07-19 04:00:03.7800000 -04:00
Run Code Online (Sandbox Code Playgroud)

你可以看到理想的特征:

2013-07-19 04:00:03.7800000 -04:00
--------------------------- ------
           |                    |
   a "local" datetime        the offset from UTC
Run Code Online (Sandbox Code Playgroud)

但相反,实际值是:

SELECT TOP 5
   ChangeDate,
   CAST(ChangeDate AS datetimeoffset) AS ChangeDateOffset
FROM AuditLog

ChangeDate               ChangeDateOffset
=======================  ==================================
2013-07-25 04:00:03.060  2013-07-25 04:00:03.0600000 +00:00
2013-07-24 04:00:03.073  2013-07-24 04:00:03.0730000 +00:00
2013-07-23 04:00:03.273  2013-07-23 04:00:03.2730000 +00:00
2013-07-20 04:00:02.870  2013-07-20 04:00:02.8700000 +00:00
2013-07-19 04:00:03.780  2013-07-19 04:00:03.7800000 +00:00
Run Code Online (Sandbox Code Playgroud)

具有无效特征:

2013-07-19 04:00:03.7800000 +00:00
--------------------------- ------
                              ^
                              |
                             No offset from UTC present
Run Code Online (Sandbox Code Playgroud)

所以我随机尝试其他事情:

SELECT TOP 5
    ChangeDate, 
    CAST(ChangeDate AS datetimeoffset) AS ChangeDateOffset,
    DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), ChangeDate) AS ChangeDateUTC,
    CAST(DATEADD(minute, DATEDIFF(minute, GETDATE(), GETUTCDATE()), ChangeDate) AS datetimeoffset) AS ChangeDateUTCOffset,
    SWITCHOFFSET(CAST(ChangeDate AS datetimeoffset), DATEDIFF(minute, GETUTCDATE(), GETDATE())) AS ChangeDateSwitchedOffset
FROM AuditLog
ORDER BY ChangeDate DESC
Run Code Online (Sandbox Code Playgroud)

结果如下:

ChangeDate               ChangeDateOffset                    ChangeDateUTC            ChangeDateUTCOffset                 ChangeDateSwitchedOffset
=======================  ==================================  =======================  ==================================  ==================================
2013-07-25 04:00:03.060  2013-07-25 04:00:03.0600000 +00:00  2013-07-25 08:00:03.060  2013-07-25 08:00:03.0600000 +00:00  2013-07-25 00:00:03.0600000 -04:00
2013-07-24 04:00:03.073  2013-07-24 04:00:03.0730000 +00:00  2013-07-24 08:00:03.073  2013-07-24 08:00:03.0730000 +00:00  2013-07-24 00:00:03.0730000 -04:00
2013-07-23 04:00:03.273  2013-07-23 04:00:03.2730000 +00:00  2013-07-23 08:00:03.273  2013-07-23 08:00:03.2730000 +00:00  2013-07-23 00:00:03.2730000 -04:00
2013-07-20 04:00:02.870  2013-07-20 04:00:02.8700000 +00:00  2013-07-20 08:00:02.870  2013-07-20 08:00:02.8700000 +00:00  2013-07-20 00:00:02.8700000 -04:00
2013-07-19 04:00:03.780  2013-07-19 04:00:03.7800000 +00:00  2013-07-19 08:00:03.780  2013-07-19 08:00:03.7800000 +00:00  2013-07-19 00:00:03.7800000 -04:00
                         ----------------------------------                           ----------------------------------  ----------------------------------
                                              No UTC offset                           Time in UTC          No UTC offset  Time all wrong
Run Code Online (Sandbox Code Playgroud)

它们都没有返回所需的值.

任何人都可以建议回归我直觉所需的东西吗?

Ian*_*oyd 63

我想到了.诀窍是有一个内置的SQL Server函数ToDateTimeOffset,它将任意偏移信息附加到任何提供的datetime.

例如,相同的查询:

SELECT ToDateTimeOffset('2013-07-25 15:35:27', -240)     --  -240 minutes
SELECT ToDateTimeOffset('2013-07-25 15:35:27', '-04:00') --  -4 hours
Run Code Online (Sandbox Code Playgroud)

两者都回归:

2013-07-25 15:35:27.0000000 -04:00
Run Code Online (Sandbox Code Playgroud)

注意:offset参数ToDateTimeOffset可以是:

  • 一个integer,代表几分钟
  • a string,代表小时和分钟({+|-}TZH:THM格式)

我们需要服务器的当前UTC偏移量

接下来我们需要服务器与UTC的当前偏移量.我有两种方法可以让SQL Server返回integerUTC的分钟数:

DATEPART(TZOFFSET, SYSDATETIMEOFFSET()) 
DATEDIFF(minute, GETUTCDATE(), GETDATE())
Run Code Online (Sandbox Code Playgroud)

都归来了

-240
Run Code Online (Sandbox Code Playgroud)

将其插入TODATETIMEOFFSET功能:

SELECT ToDateTimeOffset(
      '2013-07-25 15:35:27',
      DATEPART(TZOFFSET, SYSDATETIMEOFFSET()) --e.g. -240
)
Run Code Online (Sandbox Code Playgroud)

返回datetimeoffset我想要的值:

2013-07-25 15:35:27.0000000 -04:00
Run Code Online (Sandbox Code Playgroud)

完全放在一起

现在我们可以有更好的功能将datetime转换为datetimeoffset:

CREATE FUNCTION dbo.ToDateTimeOffset(@value datetime2)
    RETURNS datetimeoffset AS
BEGIN
/*
    Converts a date/time without any timezone offset into a datetimeoffset value, 
    using the server's current offset from UTC. 

    For this we use the builtin ToDateTimeOffset function; 
    which attaches timezone offset information with a datetimeoffset value.

    The trick is to use DATEDIFF(minutes) between local server time and UTC 
    to get the offset parameter.

    For example:
        DATEPART(TZOFFSET, SYSDATETIMEOFFSET())
    returns the integer
        -240

    for people in EDT (Eastern Daylight Time), which is 4 hours (240 minutes) behind UTC.
    Pass that value to the SQL Server function:
        TODATETIMEOFFSET(@value, -240)
*/

    RETURN TODATETIMEOFFSET(@value, DATEPART(TZOFFSET, SYSDATETIMEOFFSET()))
END;
Run Code Online (Sandbox Code Playgroud)

样品用法

SELECT TOP 5
    ChangeDate, 
    dbo.ToDateTimeOffset(ChangeDate) AS ChangeDateOffset
FROM AuditLog
Run Code Online (Sandbox Code Playgroud)

返回所需的:

ChangeDate               ChangeDateOffset
=======================  ==================================
2013-07-25 04:00:03.060  2013-07-25 04:00:03.0600000 -04:00
2013-07-24 04:00:03.073  2013-07-24 04:00:03.0730000 -04:00
2013-07-23 04:00:03.273  2013-07-23 04:00:03.2730000 -04:00
2013-07-20 04:00:02.870  2013-07-20 04:00:02.8700000 -04:00
2013-07-19 04:00:03.780  2013-07-19 04:00:03.7800000 -04:00
Run Code Online (Sandbox Code Playgroud)

如果内置函数只是这样做,那将是理想的:

TODATETIMEOFFSET(value)
Run Code Online (Sandbox Code Playgroud)

而不是必须创建一个"过载":

dbo.ToDateTimeOffset(value)
Run Code Online (Sandbox Code Playgroud)

注意:任何代码都将发布到公共域中.无需归属.

  • 这非常好 - 除了它假定服务器的*current*offset是对所有数据有效的.如果原始时间是在标准偏移和日光偏移之间交替的时区中记录的,那么这并不能解释这一点. (22认同)
  • 自从这篇文章发布以来已经过去2年了,但正如@MattJohnson所说,我的数据可以追溯到3年前,当我们从标准时间切换到夏令时时,这并没有考虑到这一点.有人找到了更好的方法吗? (3认同)
  • @RoLYroLLs - SQL Server 2016添加了时区支持,或者您可以使用我的项目.两者的信息都在这里:https://github.com/mj1856/SqlServerTimeZoneSupport (3认同)