UTC和夏令时的情况

5
我正在使用UTC来存储数据库中的日期和时间值。这些值将在客户端或按客户端时区转换为本地时间。我从MSDN文章中了解到,从UTC显示时间似乎在夏令时期间存在问题。
引用:
某个居住在美国东海岸的人键入一个值,例如“2003年10月26日01:10:00 AM”。
1)在特定的早晨,由于夏令时,在凌晨2:00,当地时钟被重置为凌晨1:00,创造了一个25小时的一天。由于在上午1:00至2:00之间的所有时钟时间值在那个特定的早晨都会发生两次,至少在美国和加拿大的大部分地区,计算机实际上无法知道指的是哪个1:10 AM - 是在切换之前还是在夏令时调整后的10分钟之后。
2)同样,在春季的某个早晨,不存在2:10 AM这样的时间。原因是在那个特定的早晨,当地时钟在凌晨2:00突然更改为凌晨3:00。整个2:00小时在这个23小时的一天中从未出现过。
如何处理情况#1,当您可能有4个交易时,在夏令时调整之前和之后各有两个?如何向用户显示交易的时间,因为由于时区偏移,最后两个交易可能比前两个交易出现更早的时间。有时,这可能是不合逻辑的,例如在邮件链中。
添加:
为了添加更多关于上下文的信息,RIA应用程序(如Silverlight/Flash)在客户端(或通过Webservice与服务器通信的任何客户端应用程序)上运行,允许用户选择投递时间或按本地PC时间安排。
如果我可以检查给定的输入时间是否无效,我可能会提醒用户。此外,对于旅行者,需要在时间点上找到时区,而不是基于用户选择,因为他们可能在不同的时区之间移动,并保存其时区设置在用户配置文件中将无济于事。
以下是一些用于评估输入时间的C#测试样例:
//2:30 am CT to UTC --> 8:30 am  
DateTime dt = new DateTime(2009, 03, 08, 2, 30, 00, DateTimeKind.Local);  

//8:30 am UTC to CT --> 3:30 am.. which is as expected  
DateTime dt1 = new DateTime(2009, 03, 08, 8, 30, 00, DateTimeKind.Utc);  

//check for daylight saving time returns false.. ??  
TimeZoneInfo.Local.IsDaylightSavingTime(dt);  

//check for daylight saving time returns true  
TimeZoneInfo.Local.IsInvalidTime(dt);  
7个回答

4
你需要存储时间偏移量。
目前东海岸的时间为(往返格式)。
2009-08-11T13:22:13.8493713-04:00

即使东海岸被认为是在-5时区,在夏令时期间,时间将会在-4时区。
10月26日凌晨01:10,时间将会是:
2009-10-26T1:10:00.0000000-04:00

但是当时钟超过02:00并且我们切换回正常时间时,你的时间将会改变。
2009-10-26T1:10:00.0000000-05:00

为了处理偏移量,.NET从2.0sp1开始提供了类型DateTimeOffset。Microsoft SQL Server 2008也提供了数据类型datetimeoffset来帮助您存储该值。如果您没有使用Microsoft SQL Server 2008,可以使用往返格式将日期存储为字符串:
DateTime.Now.ToString("o")

3
这些情况是为使用DST而辩护的案例。只要在UTC中存储和排序值,显示什么并不重要。也就是说,如果正确使用UTC,则可以解决这些情况中出现的问题。
是的,看到像这样的记录可能会让人困惑:12:30、1:20、1:10、3:30,但如果按照UTC(实际发生的情况)进行排序,我认为这是正确的做法。
通过记录所有内容并以UTC或相对时间(如“17分钟前...”)显示来避免这个问题。
如果您正在引用用户提供的日期/时间(如评论中所建议的),我有一些坏消息告诉您:它很糟糕。我认为最好、最明显的解决方案是选择一个规则并执行它。如果您确实需要完美处理它,您的用户界面将需要扩展以严谨地处理每年仅出现一小时的这种边缘情况,然后仅适用于非实时创建的交易(因为如果它们是实时的,您将知道DST的等效时间)。

1
这个问题在问如何处理用户输入的日期发生在夏令时期间的情况。将它们存储为UTC很容易,但是当你甚至不确定应该是什么时间时,如何转换为UTC呢? - Jon Tackabury
啊,我明白了。这大概是为了一个离线应用程序,服务器时间没有帮助吧? - Michael Haren
1
获取服务器时间并将其存储为UTC很容易。所提出的场景涉及用户输入的日期,这是一个完全不同的问题。 - Jon Tackabury
不要忘记并非每个地方都会进行夏令时调整,即使在美国也是如此!亚利桑那州(除了纳瓦霍族(我想))和夏威夷以及波多黎各、维尔京群岛、关岛和美属萨摩亚这些美国领土全年使用标准时间。 - Adam Porad

1
如何向用户显示交易时间,因为最后两笔交易可能会比前两笔交易早出现,这是由于时区的变化造成的。有时候,这可能会证明是不合逻辑的,例如在邮件链中。

按照协调世界时(UTC)排序,并以本地时间显示它们。

因此,用户可能会看到像这样的列表:

01:10:00
01:50:00
01:05:00
01:20:00

要么这样,要么按照协调世界时(UTC)显示并排序它们。

1
如果有人手动输入数据,除非他们以UTC时间输入,否则祝你好运。在处理此类情况时,其实并没有“正确”的方式。如果你正在处理非用户输入的日期,比如交易发生的时间,只需将所有这些日期存储为UTC时间,那么一切都很好。 :)

1

一般来说,您的问题有两个部分:

  1. 接收用户输入
  2. 向用户显示数据

这两个方面应该采用类似的处理方式,但在某些方面有着不同的解决方法。对于第一个问题,最简单的选择是确定您的业务是否真正需要在特定小时内指定时间的明确方式。许多应用程序只是忽略它(您上次使用具有此功能的日期选择器是什么时候?)并假设任何一致的猜测算法都足够了。如果输入了不存在的小时数,您应该提供保障(即抛出错误)。

关于第二点,跳过的小时并不重要,因为您的数据库是以UTC为基准的。重复的小时可能会让人感到困惑,特别是在时间戳的序列中。如果值得的话,考虑使用时区标识符格式化日期字符串,其中包括对夏令时偏移量的引用。大多数旧式的时区名称都包括这一点(如EST vs EDT,GMT vs BST等)。这将足以消除歧义,在紧急情况下。这可能是您所需要的全部,因为这种边界情况可能不值得在显示上花费太多精力。如果您需要更多输出,还可以使用UTC偏移量格式化时间戳,这将使时间戳序列中的变更非常明确。

0

我已经针对新西兰时间解决了这个问题:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

-- =============================================
-- Author:      Vivi Woolford
-- Create date: 27-09-2016
-- Description: This procedure only Works in New Zealand
-- =============================================
CREATE FUNCTION [dbo].[udf_GetLocalTimeFromUTC] 
(   
    @UTCTime DATETIME
)
RETURNS DATETIME
AS
BEGIN
    --Daylight Saving commences on the last Sunday in September, when 2.00am becomes 3.00am. 
    --It ends on the first Sunday in April, when 3.00am becomes 2.00am.
    DECLARE @LocalTime DATETIME 
    DECLARE @OffSet INT = 12

    SELECT @LocalTime = DATEADD(HOUR, @OffSet, @UTCTime)

    IF @LocalTime BETWEEN 
    /*FINISH DAY LIGHT SAVINGS*/
    DATEADD(HOUR, 2, DATEADD(dd, (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(@LocalTime)-1900) * 12 + 3,0))%7)),DATEADD(mm,(YEAR(@LocalTime)-1900) * 12 + 3,0)))
    AND     
    /*START DAY LIGHT SAVINGS*/
    DATEADD(HOUR, 2, DATEADD(dd, -1*(DATEPART(dw, DateAdd(day, -1, DateAdd(month, DateDiff(month, 0, @LocalTime)+1, 0)))-1),DateAdd(day, -1, DateAdd(month, DateDiff(month, 0, @LocalTime)+1, 0))))     
    BEGIN
        SELECT @LocalTime = @LocalTime 
    END 
    ELSE
    BEGIN
        SELECT @LocalTime = DATEADD(HOUR, 1, @LocalTime)
    END

    RETURN @LocalTime

END

GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      Vivi Woolford
-- Create date: 27-09-2016
-- Description: This procedure only Works in New Zealand
-- =============================================
ALTER FUNCTION [dbo].[udf_GetUTCFromLocalTime]
(   
    @LocalTime DATETIME
)
RETURNS DATETIME
AS
BEGIN
    --Daylight Saving commences on the last Sunday in September, when 2.00am becomes 3.00am. 
    --It ends on the first Sunday in April, when 3.00am becomes 2.00am.
    DECLARE @UTCTime DATETIME   
    DECLARE @OffSet INT = 12

    IF @LocalTime BETWEEN 
    /*FINISH DAY LIGHT SAVINGS*/
    DATEADD(HOUR, 2, DATEADD(dd, (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(@LocalTime)-1900) * 12 + 3,0))%7)),DATEADD(mm,(YEAR(@LocalTime)-1900) * 12 + 3,0)))
    AND     
    /*START DAY LIGHT SAVINGS*/
    DATEADD(HOUR, 2, DATEADD(dd, -1*(DATEPART(dw, DateAdd(day, -1, DateAdd(month, DateDiff(month, 0, @LocalTime)+1, 0)))-1),DateAdd(day, -1, DateAdd(month, DateDiff(month, 0, @LocalTime)+1, 0))))
    BEGIN
        SELECT @UTCTime = DATEADD(HOUR, -@OffSet, @LocalTime)
    END 
    ELSE 
    BEGIN
        SELECT @UTCTime = DATEADD(HOUR, -1-@OffSet, @LocalTime)
    END

    RETURN @UTCTime

END
go

0
SQL Server有一个新类型,'datetimeoffset',它可以很好地处理这个问题,因为您可以使用不同的偏移量来表示相同的时间。对于我的SQL Server 2005数据库,我使用该类型的字符串文字,即形式为"YYYY-MM-DD hh:mm:ss-hh:mm"的nvarchar(25)。
为此,我创建了例程来将这些字符串转换为正确的时间。

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