Jam*_*mes 50 sql-server datetime utc
我最近更新了我的系统,将日期/时间记录为UTC,因为它们以前存储为本地时间.
我现在需要将所有本地存储的日期/时间转换为UTC.我想知道是否有任何内置函数,类似于.NET的ConvertTime方法?
我试图避免编写实用程序的应用程序为我这样做.
有什么建议?
小智 75
我不相信上面的代码会起作用.原因是它取决于当地日期和UTC时间之间的差异.例如,在加利福尼亚,我们现在在PDT(太平洋夏令时); 此时间与UTC之间的差异为7小时.如果现在运行,所提供的代码将在每个希望转换的日期添加7个小时.但是,如果转换了历史存储日期或将来某个日期,并且该日期不是夏令时期间,则当正确的偏移量为8时,它仍会添加7.底线:您无法正确转换日期/时间通过仅查看当前日期,在时区之间(包括不遵守夏令时的UTC).您必须考虑要转换的日期本身,以及日期时间是否在该日期生效.此外,白天和标准时间自身发生变化的日期也发生了变化(乔治布什在美国执政期间更改了日期!).换句话说,任何甚至引用getdate()或getutcdate()的解决方案都不起作用.它必须解析要转换的实际日期.
Mat*_*int 40
使用SQL Server 2016,现在可以使用该AT TIME ZONE语句内置对时区的支持.您可以链接这些进行转换:
SELECT YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time' AT TIME ZONE 'UTC'
Run Code Online (Sandbox Code Playgroud)
或者,这也可以:
SELECT SWITCHOFFSET(YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time', '+00:00')
Run Code Online (Sandbox Code Playgroud)
这些中的任何一个都将解释太平洋时间的输入,正确地说明DST是否生效,然后转换为UTC.结果将是datetimeoffset零偏移.
Mic*_*per 16
如前所述,没有内置方法可以在SQL Server中执行时区规则感知日期转换(至少从SQL Server 2012开始).
您基本上有三种选择:
虽然SQL Server不提供执行时区规则感知日期转换的工具,但.NET框架确实如此,只要您可以使用SQL CLR,就可以利用它.
在Visual Studio 2012中,确保安装了数据工具(否则,SQL Server项目不会显示为选项),并创建新的SQL Server项目.
然后,添加一个新的SQL CLR C#用户定义函数,将其命名为"ConvertToUtc".VS将为您生成样板,看起来像这样:
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString ConvertToUtc()
{
// Put your code here
return new SqlString (string.Empty);
}
}
Run Code Online (Sandbox Code Playgroud)
我们想在这里做一些改变.首先,我们希望返回一个SqlDateTime而不是一个SqlString.其次,我们想做一些有用的事情.:)
您修改后的代码应如下所示:
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlDateTime ConvertToUtc(SqlDateTime sqlLocalDate)
{
// convert to UTC and use explicit conversion
// to return a SqlDateTime
return TimeZone.CurrentTimeZone.ToUniversalTime(sqlLocalDate.Value);
}
}
Run Code Online (Sandbox Code Playgroud)
在这一点上,我们准备尝试一下.最简单的方法是在Visual Studio中使用内置的Publish工具.右键单击数据库项目,然后选择"发布".设置数据库连接和名称,然后单击"发布"将代码推送到数据库中或单击"生成脚本",如果您要为后代存储脚本(或将位推送到生产中).
在数据库中安装UDF后,您可以看到它的运行情况:
declare @dt as datetime
set @dt = '12/1/2013 1:00 pm'
select dbo.ConvertToUtc(@dt)
Run Code Online (Sandbox Code Playgroud)
Sql*_*yan 14
如果他们都是你的本地人,那么这是抵消:
SELECT GETDATE() AS CurrentTime, GETUTCDATE() AS UTCTime
Run Code Online (Sandbox Code Playgroud)
你应该能够使用以下方法更新所有数据:
UPDATE SomeTable
SET DateTimeStamp = DATEADD(hh, DATEDIFF(hh, GETDATE(), GETUTCDATE()), DateTimeStamp)
Run Code Online (Sandbox Code Playgroud)
那会有用吗,还是我错过了这个问题的另一个角度?
这是一个测试过程,将我的数据库从本地升级到utc时间.升级数据库所需的唯一输入是输入本地时间从utc时间偏移到@Offset的分钟数,以及通过设置@ApplyDaylightSavings来调整时区的夏令时.
例如,美国中部时间将输入@ Offset = -360和@ ApplyDaylightSavings = 1持续6小时,是的应用夏令时调整.
支持数据库功能
CREATE FUNCTION [dbo].[GetUtcDateTime](@LocalDateTime DATETIME, @Offset smallint, @ApplyDaylightSavings bit)
RETURNS DATETIME AS BEGIN
--====================================================
--Calculate the Offset Datetime
--====================================================
DECLARE @UtcDateTime AS DATETIME
SET @UtcDateTime = DATEADD(MINUTE, @Offset * -1, @LocalDateTime)
IF @ApplyDaylightSavings = 0 RETURN @UtcDateTime;
--====================================================
--Calculate the DST Offset for the UDT Datetime
--====================================================
DECLARE @Year as SMALLINT
DECLARE @DSTStartDate AS DATETIME
DECLARE @DSTEndDate AS DATETIME
--Get Year
SET @Year = YEAR(@LocalDateTime)
--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)
--Finally add the DST Offset if needed
RETURN CASE WHEN @LocalDateTime BETWEEN @DSTStartDate AND @DSTEndDate THEN
DATEADD(MINUTE, -60, @UtcDateTime)
ELSE
@UtcDateTime
END
END
GO
Run Code Online (Sandbox Code Playgroud)
升级脚本
begin try
begin transaction;
declare @sql nvarchar(max), @Offset smallint, @ApplyDaylightSavings bit;
set @Offset = -360; --US Central Time, -300 for US Eastern Time, -480 for US West Coast
set @ApplyDaylightSavings = 1; --1 for most US time zones except Arizona which doesn't observer daylight savings, 0 for most time zones outside the US
declare rs cursor for
select 'update [' + a.TABLE_SCHEMA + '].[' + a.TABLE_NAME + '] set [' + a.COLUMN_NAME + '] = dbo.GetUtcDateTime([' + a.COLUMN_NAME + '], ' + cast(@Offset as nvarchar) + ', ' + cast(@ApplyDaylightSavings as nvarchar) + ') ;'
from INFORMATION_SCHEMA.COLUMNS a
inner join INFORMATION_SCHEMA.TABLES b on a.TABLE_SCHEMA = b.TABLE_SCHEMA and a.TABLE_NAME = b.TABLE_NAME
where a.DATA_TYPE = 'datetime' and b.TABLE_TYPE = 'BASE TABLE' ;
open rs;
fetch next from rs into @sql;
while @@FETCH_STATUS = 0 begin
exec sp_executesql @sql;
print @sql;
fetch next from rs into @sql;
end
close rs;
deallocate rs;
commit transaction;
end try
begin catch
close rs;
deallocate rs;
declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
rollback transaction;
raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch
Run Code Online (Sandbox Code Playgroud)
如果必须将除今天以外的日期转换为不同的时区,则必须处理夏令时。我想要一种无需担心数据库版本,不使用存储函数以及可以轻松移植到Oracle的解决方案。
我认为沃伦(Warren)可以正确选择夏令时,但是要使其在多个时区和国家/地区使用不同的规则(甚至是2006年至2007年间在美国发生变化的规则)中更加有用,上述解决方案。请注意,这不仅有我们的时区,而且还有中欧。中欧遵循4月的最后一个星期日和10月的最后一个星期日。您还会注意到,2006年美国遵循旧的第一个星期日(四月),即10月的最后一个星期日。
该SQL代码可能看起来有些丑陋,但是只需将其复制并粘贴到SQL Server中并进行尝试。请注意,年份,时区和规则共有3个部分。如果要再一年,只需将其添加到年份联盟中即可。相同的另一个时区或规则。
select yr, zone, standard, daylight, rulename, strule, edrule, yrstart, yrend,
dateadd(day, (stdowref + stweekadd), stmonthref) dstlow,
dateadd(day, (eddowref + edweekadd), edmonthref) dsthigh
from (
select yrs.yr, z.zone, z.standard, z.daylight, z.rulename, r.strule, r.edrule,
yrs.yr + '-01-01 00:00:00' yrstart,
yrs.yr + '-12-31 23:59:59' yrend,
yrs.yr + r.stdtpart + ' ' + r.cngtime stmonthref,
yrs.yr + r.eddtpart + ' ' + r.cngtime edmonthref,
case when r.strule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.stdtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.stdtpart) end
else (datepart(dw, yrs.yr + r.stdtpart) - 1) * -1 end stdowref,
case when r.edrule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.eddtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.eddtpart) end
else (datepart(dw, yrs.yr + r.eddtpart) - 1) * -1 end eddowref,
datename(dw, yrs.yr + r.stdtpart) stdow,
datename(dw, yrs.yr + r.eddtpart) eddow,
case when r.strule in ('1', '2', '3') then (7 * CAST(r.strule AS Integer)) - 7 else 0 end stweekadd,
case when r.edrule in ('1', '2', '3') then (7 * CAST(r.edrule AS Integer)) - 7 else 0 end edweekadd
from (
select '2005' yr union select '2006' yr -- old us rules
UNION select '2007' yr UNION select '2008' yr UNION select '2009' yr UNION select '2010' yr UNION select '2011' yr
UNION select '2012' yr UNION select '2013' yr UNION select '2014' yr UNION select '2015' yr UNION select '2016' yr
UNION select '2017' yr UNION select '2018' yr UNION select '2018' yr UNION select '2020' yr UNION select '2021' yr
UNION select '2022' yr UNION select '2023' yr UNION select '2024' yr UNION select '2025' yr UNION select '2026' yr
) yrs
cross join (
SELECT 'ET' zone, '-05:00' standard, '-04:00' daylight, 'US' rulename
UNION SELECT 'CT' zone, '-06:00' standard, '-05:00' daylight, 'US' rulename
UNION SELECT 'MT' zone, '-07:00' standard, '-06:00' daylight, 'US' rulename
UNION SELECT 'PT' zone, '-08:00' standard, '-07:00' daylight, 'US' rulename
UNION SELECT 'CET' zone, '+01:00' standard, '+02:00' daylight, 'EU' rulename
) z
join (
SELECT 'US' rulename, '2' strule, '-03-01' stdtpart, '1' edrule, '-11-01' eddtpart, 2007 firstyr, 2099 lastyr, '02:00:00' cngtime
UNION SELECT 'US' rulename, '1' strule, '-04-01' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2006 lastyr, '02:00:00' cngtime
UNION SELECT 'EU' rulename, 'L' strule, '-03-31' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2099 lastyr, '01:00:00' cngtime
) r on r.rulename = z.rulename
and datepart(year, yrs.yr) between firstyr and lastyr
) dstdates
Run Code Online (Sandbox Code Playgroud)
对于规则,在第一个,第二个,第三个或最后一个星期日使用1、2、3或L。日期部分给出了月份,并且取决于规则,规则类型L为月份的第一天或月份的最后一天。
我将上面的查询放入视图中。现在,只要我想要一个带时区偏移量的日期或转换为UTC时间的日期,我都只需加入此视图并选择以日期格式获取日期即可。我将其转换为datetimeoffset而不是datetime。
select createdon, dst.zone
, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end pacificoffsettime
, TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end) pacifictime
, SWITCHOFFSET(TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end), '+00:00') utctime
from (select '2014-01-01 12:00:00' createdon union select '2014-06-01 12:00:00' createdon) photos
left join US_DAYLIGHT_DATES dst on createdon between yrstart and yrend and zone = 'PT'
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
117249 次 |
| 最近记录: |