SQL服务器:将普通日期列数据转换为带时区的日期

SQL Server: convert common date column data to date with timezone

我正在努力解决这个问题:我有一个名为 Foo 的 table,其中有一个 datetime 列(没有时区信息)。但是,我们正在做这样的 "internationalization" 工作,现在我们需要存储带有时区信息的日期时间(datetimeoffset 列类型)。

假设所有预先存在的日期时间数据都使用 -03:00 (America/São Paulo - Brasília) 时区,并且 CONVERT(varchar(50), foo.date, 127) returns 类似于 2012-04-17T01:14:33 ,我应该如何继续获取 ISO 8601 日期时间字符串(类似于 2012-04-17T04:14:33Z2012-04-17T01:14:33 -03:00)?

尝试使用此命令。它对我有用。

SELECT CAST(<ColumnName> AS datetimeoffset)
FROM <TableName>

如果您想更新表格信息,我会试试这个。

UPDATE <TableName>
SET <DateColumn> = CONVERT(DATETIMEOFFSET, <DateColumn>)
WHERE ...

希望对您有所帮助SWITCHOFFSET

语法

SWITCHOFFSET ( DATETIMEOFFSET, time_zone ) 

代码

--SELECT SWITCHOFFSET (ColDatetimeoffset, '-03:00') 
SELECT SYSDATETIMEOFFSET() CurrentOffset
SELECT SWITCHOFFSET (SYSDATETIMEOFFSET(), '-03:00') 

也看到这个CAST and CONVERT

SELECT CONVERT(VARCHAR, GETDATE(), 126)
SELECT CONVERT(VARCHAR, GETDATE(), 127)

如果使用 .NET CLR 函数,您可以获得更好的格式化程序。为此,您需要安装 Visual Studio 版本。创建数据库项目:

从数据库项目中右键单击并执行添加 -> 新项.. Select SQL CLR C# 用户定义函数 然后将其剪切并粘贴到您的 class

public partial class UserDefinedFunctions
{
    [SqlFunction(Name = "FormatDate")]
    public static SqlString FormatDate(SqlDateTime date, SqlString formatString)
    {
        var thisDate = date.Value;
        return new SqlString (thisDate.ToString(formatString.Value));
    }
    [SqlFunction(Name = "FormatDateTimeOffset")]
    public static SqlString FormatDateTimeOffset(DateTimeOffset date, SqlString formatString)
    {
        return new SqlString(date.ToString(formatString.Value));
    }    
}

右键单击该项目并 select 发布,输入您的数据库连接信息并发布,然后您可以 运行 查询,例如:

select dbo.FormatDate(getdate(), 'yyyy-MM-dd HH:mm:ss ''GMT'' zzz') as "DateTime"
select dbo.FormatDateTimeOffset(convert(datetimeoffset, getdate()), 'yyyy-MM-dd HH:mm:ss K') as "DateTimeOffset"    

...并得到如下结果:

首先,要认识到巴西利亚和圣保罗在一年中的部分时间都观察到 daylight saving time,因此这不仅仅是向 -03:00 的简单转换。如果它被固定到那个偏移量,你可以只使用 TODATETIMEOFFSET 函数 - 但你不能。有两种不同的偏移量需要考虑。 -03:00 用于标准时间,-02:00 用于夏令时。

确定夏令时在巴西何时生效并不是一件容易的事情,因为变化的历史非常复杂。可以找到当前规则 here in the tzdb, which currently says (since 2008) to start DST on the 3rd Sunday in October, and end it on the 3rd Sunday in February - unless that day happens to be the Carnival Sunday,在这种情况下,DST 将在 2 月的第 4 个星期日结束。

当然,您可以将这些规则编入存储过程或用户定义的函数中,但这会非常复杂。

有两种更好的方法值得考虑:

  1. 您可以编写一个 SQL CLR 函数。如果这样做,您有两个子选项:

    • 如果您要进行转换的服务器的本地时区设置为巴西利亚,那么您可以简单地使用 new DateTimeOffset(yourDateTime) - 这将根据本地时区。

    • 您可以使用 TimeZoneInfo class,但需要启用 Unsafe Mode。如果这样做,转换将如下所示:

      var tz = TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time");
      var dto = new DateTimeOffset(yourDateTime, tz.GetUtcOffset(yourDateTime));
      
  2. 您可以使用我的 SQL Server Time Zone Support 项目,它整合了所有正确的时区转换,并为您提供了一些准备就绪的简单功能。

    DECLARE @tz varchar(25)
    SET @tz = 'America/Sao_Paulo';
    SELECT Tzdb.SwitchZone(Tzdb.LocalToUtc(YourDateTime, @tz, 1, 1), @tz);
    

    请注意,我先将本地时间转换为UTC,然后将其切换回原始时区。我会 probably update the project to include this as a single function,但现在这两个可以工作。

当然,对于这些选项中的任何一个,您都需要编写一个 UPDATE 语句。我建议创建一个新列,然后更新该列,然后仅在您对转换感到满意后才删除原始列。不要试图就地更改现有列的类型。或者,您可以创建一个新的 table 并通过从一个 table 中选择并插入另一个来进行转换。

最后,考虑一下当时区有 DST 时,任何时候您从本地时间转换为瞬时时间点(无论是 UTC datetime,还是 datetimeoffset)-可能存在歧义。发生这种情况是因为 "fall-back" 转换,它会在 DST 结束时创建本地时间值的重叠。例如,在您所在的时区,夏令时下一次开始于 2015-10-18 00:00:00,结束于 2016-02-21 00:00:00。当 DST 结束时,从 2016-02-20 23:00:002016-02-20 23:59:59.9999999 的值是不明确的。如果它们指的是 夏令时结束之前发生的事件,它们可能有 -02:00 的偏移量,或者如果它们指的是 -03:00 的偏移量DST 结束 之后发生的事件。

在我上面的建议中,建议 #1 中的两个选项都会选择 later 以防出现歧义。如果您采用建议 #2,则可以使用 LocalToUtc 函数上的位标志来控制此行为。我上面显示的选项在不明确的情况下选择了 earlier 出现 - 这通常是首选(恕我直言)。 The project's readme file 详细介绍了这些选项以及它们如何控制模糊时间和无效时间行为。

还要考虑,如果您的数据库中有一些其他的增量列,例如整数 ID 列,并且您碰巧在转换前后的不明确期间有行,您可能能够按该辅助列对它们进行排序以消除歧义。这通常需要在歧义期间手动审查数据 - 或者更新语句中的一些其他花哨的步法,但在某些情况下是可能的。