A PHP Error was encountered

Severity: Notice

Message: Undefined index: sread

Filename: controllers/Post.php

Line Number: 103

Backtrace:

File: D:\wwwroot\changweihua\wwwroot\application\controllers\Post.php
Line: 103
Function: _error_handler

File: D:\wwwroot\changweihua\wwwroot\index.php
Line: 292
Function: require_once

Azure 上SQL Database(PaaS)Time Zone时区问题处理
返回

Azure 上SQL Database(PaaS)Time Zone时区问题处理

Azure 上SQL Database(PaaS)Time Zone时区问题处理


AT Time Zone

在Azure上面的PaaS时间都是以UTC时间为准(云的世界里基本上都是以UTC时间为标准的),所以以前在本地SQL Server上面常用的GetDate()方法会碰到问题,在中国获取的时间会被当前时间少了8个小时,因为Azure上的时间都是UTC之间,中国的时区是+8.所以你通过GetDate()获取的时间少了8个小时是正常的。但是碰到这个问题有什么好办法可以解决呢?怎样才能获取带上时区偏移之后的时间呢?。在Azure SQL Database上面是没有办法直接通过设置某个参数或者变量就可以解决的。不过SQL Database V12之后开始支持SQL Server 2016才支持的一个新的语法:AT Time Zone

Syntax inputdate AT TIME ZONE timezone

Arguments inputdate Is an expression that can be resolved to a smalldatetime, datetime, datetime2, or datetimeoffset value. timezone Name of the destination time zone. SQL Server relies on time zones that are stored in the Windows Registry. All time zones installed on the computer are stored in the following registry hive: KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones. A list of installed time zones is also exposed through the sys.time_zone_info (Transact-SQL) view.

有了这个新的语法之后我们解决这个时区的问题就容易一些了。

假设我们有一张表appcount有两个字段一个是自增长字段id,和另外一个字段是日期型字段Createdate

CREATE TABLE [dbo].[appcount]( 
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [createtime] [datetime] NULL, 
    PRIMARY KEY CLUSTERED (Id) 
)

通常我们往这个表里面插进去一条数据时会用下面这个语句:

INSERT INTO appcount(createtime) VALUES(getdate())

用来标记着这条记录的创建时间是当前时间的,但是直接在Azure SQL Database上面执行出来会变成这个结果:

如果我们在显示这个记录创建时间的时候不带上Time Zone相关的处理,时间就跟当前时间对不上号了。

如果要获取到当前时区的时间的话,我们可以将GetDate()这个方法稍微改一下

INSERT INTO appcount(createtime) VALUES(CONVERT(DATETIME,SYSDATETIMEOFFSET() AT TIME ZONE 'China Standard Time'))


从图里我们会发现时间变回了我们想要的本地时间了

注意:因为AT Time Zone语法是Azure SQL Database V12的功能,所以如果你的数据库不是V12版本的话是不支持这个语法的,你执行语句是会抛出下面的错误


老版本的的Azure SQL Datbase解决办法可以参考下面这个方法,通过自定义函数来解决

自定义函数

Create a User-Defined Function: GetLocalDate()

CREATE FUNCTION [dbo].[GetLocalDate]
(
    @TimezoneDiffInHour TINYINT = 8
    -- default set to 8 (GMT +8 = Singapore Timezone) 
)
RETURNS DATETIME
AS
BEGIN
    RETURN DATEADD(Hh, @TimezoneDiffInHour , GETUTCDATE())
END

call this function

SELECT dbo.GetLocalDate(DEFAULT)
-- OR
SELECT dbo.GetLocalDate(8)
-- 8 denotes Singapore Timezone

For anyone coming across this in future: another option (which is a lot nicer than having to make changes to the UTC offset in this function every time daylight saving time starts or ends!) is to use a new SQL Azure feature:

RETURN CONVERT(datetime, SYSDATETIMEOFFSET() AT TIME ZONE ‘Time Zone Name’)

You can pass in the name of a Windows time zone (e.g. ‘GMT Standard Time’), and this will always return the correct time, taking account of daylight saving time shifts.

进阶篇

通常ISV在面对本地客户时对时间相关的处理,一般都时区信息都是不敏感的。但是现在云的世界里为了让大家把时间处理的方式统一起来,云上的服务都是以UTC时间为准的,现在如果作为一个ISV来说就算你面对的客户只是本地用户但是你打算利用云来为你进行的应用提供更多的功能和便捷性时,你就需要采用UTC时间来处理跟相关的代码了。

在SQL Server数据库处理时间相关的数据时,我们常常会使用DateTime类型或者DateTime2类型来处理数据,其实早在SQL Server 2008发布时,数据库就开始支持DatetimeOffset数据类型了,DatetimeOffset天生出来就是为了处理时区问题的


创建appcount表时,我们采用下面语句

CREATE TABLE [dbo].[appcount2]( 
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [createtime] [datetime] NULL, 
    [CreatetimeWithOffset] [Datetimeoffset] NULL,
    PRIMARY KEY CLUSTERED (Id) 
)

插入数据时使用

INSERT INTO    appcount(createtime,createtimewithoffset) VALUES(GetDate(),sysdatetimeoffset() AT TIME ZONE 'China Standard Time')

插入完数据我们将数据在查询出来


我们会发现DateTimeOffset在处理时区问题时不管带不带时区的迁移语句的出来的时间都是正确的,但是DateTime字段的出来的时间明显就会有时间偏移错误。

我们用下面的C#从.NET程序里面将这些时间数据取出来会如何呢?

        using (IDbConnection conn = new System.Data.SqlClient.SqlConnection(strConn))

        {
            conn.Open();
            var command = conn.CreateCommand();
            command.CommandText = "Select  id,createtime,CreatetimeWithOffset from appcount";
            var reader = command.ExecuteReader();
            while (reader.Read())
            {
                var id = reader["id"];
                var date = reader["createtime"];
                var date2 = reader["createtimeWithOffset"];
                var localDate = ((DateTimeOffset)date2).LocalDateTime;
                Console.WriteLine("id:{0},createtime:{1},DateTimeInOffSet:{2},localdate:{3}", id, date,date2, localDate);
            }
            reader.Close();
            conn.Close();
        }

这里是代码执行结果:


采用DatetimeOffset来存储时间,通过DatetimeOffset来处理时间可以让你的代码更加稳健,更加国际范