【问题标题】:UTC and Daylight savings scenarioUTC 和夏令时方案
【发布时间】:2010-11-18 16:49:05
【问题描述】:

我正在使用 UTC 在数据库中存储数据和时间值。这些值在客户端或每个客户端时区转换为本地时间。我从MSDN article 开始处理这些场景,在夏令时显示UTC 时间似乎会造成问题。

住在东海岸的人 美国输入一个值 比如“2003 年 10 月 26 日 01:10:00 AM”。

1) 在这个特别的早晨,由于 夏令时,凌晨 2:00, 本地时钟重置为凌晨 1:00, 创造一个 25 小时的一天。由于所有 凌晨 1:00 之间的时钟时间值 和 2:00 AM 发生两次 特定的早晨——至少在大多数情况下 美国和加拿大, 电脑真的没办法知道 凌晨 1:10 的意思是——那个 发生在开关之前,或一个 发生在 10 分钟后 夏令时开关。

2) 同样,问题发生在 春天的时候,在特定的 早上,没有2:10这样的时间 是。原因是 2:00 那天 特定的早晨,当地时间 时钟突然变为凌晨 3:00。 整个 2:00 小时从未发生在 这一天 23 小时。

您是如何处理第 1 种情况的,您可能有 4 笔交易,其中两笔在切换之前,两笔在切换之后在夏令时?如何向用户显示交易的时间,因为由于班次,最后两笔交易可能比前两笔交易显示的时间更早。?有时,它可能被证明是不合逻辑的,例如:在邮件链中。

添加:

要添加有关上下文的更多信息,RIA 应用程序(例如 Silverlight/Flash)在客户端上运行(或任何通过 Web 服务与服务器通信的客户端应用程序)允许用户选择交付时间或使用 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);  

【问题讨论】:

    标签: .net datetime time timezone dst


    【解决方案1】:

    这些场景是提倡使用夏令时的案例。只要您 storesort 值采用 UTC,您 显示 的内容并不重要。也就是说,如果正确使用 UTC,这些场景中出现的问题就会得到解决。

    是的,看到这样的记录会令人困惑:12:30, 1:20, 1:10, 3:30 但如果这是根据 UTC 排序的方式(实际发生的情况),我认为这就是正确的方法。

    SO 通过以 UTC 记录所有内容,然后以 UTC 或相对时间(例如“17 分钟前...”)显示所有内容,从而完全避免了这个问题。


    如果您指的是 cmets 中建议的用户提供的日期/时间,我有一些坏消息要告诉您:这很糟糕。我认为最好、最明显的解决方案是选择一个规则并按照它运行。如果您确实需要完美处理它,您的 UI 将需要扩展以迂腐地处理这种每年仅发生 1 小时的边缘情况,然后仅处理非实时创建的事务(因为如果它们是真实的-时间,你会知道 DST 等价物)。

    【讨论】:

    • 问题是询问您如何处理用户在 DST 期间输入的日期。将它们存储为 UTC 很容易,但是当您甚至不确定应该是什么时间时,如何转换为 UTC?
    • 啊,我明白了。大概这是针对断开连接的应用程序,那么,服务器时间在哪里没有帮助?
    • 获取服务器时间并将其存储为 UTC 很容易。呈现的场景是询问用户输入的日期,这是完全不同的蠕虫。
    • 不要忘记,并不是每个地方都会改变夏令时——甚至在美国也不例外!亚利桑那州(除了纳瓦霍民族(我认为))和夏威夷以及波多黎各、维尔京群岛、关岛和美属萨摩亚等美国领土全年都使用标准时间。
    【解决方案2】:

    您需要存储时间偏移量。

    目前东海岸的时间是(往返格式)

    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】:

      如何向用户显示交易的时间,因为由于班次,最后两笔交易可能会比前两笔交易显示的时间更早。?有时,它可能被证明是不合逻辑的,例如:在邮件链中。

      按 UTC 排序,并以当地时间显示。

      所以用户可能会看到这样的列表:

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

      要么这样,要么显示按UTC排序。

      【讨论】:

        【解决方案4】:

        如果有人手动输入数据,祝你好运,除非他们在 UTC 时间输入数据。没有真正的“正确”方法来处理它。如果您正在处理用户未输入的日期,例如交易发生的时间,只需将这些都存储为 UTC,生活就会很好。 :)

        【讨论】:

          【解决方案5】:

          您的问题一般分为两部分:

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

          这两者应该以类似方式处理,但在某些方面有一些不同的解决方法。至于 1,最简单的选择是确定您的业务是否真的需要一种明确的方式来指定特定时间的时间。许多应用程序只是忽略了它(您最后一次使用提供它的日期选择器是什么时候?)并简单地假设任何一致的猜测算法就足够了。如果输入了不存在的时间,您应该提供保护措施(即抛出错误)。

          至于 2,跳过的时间无关紧要,因为您的数据库是 UTC。重复小时可能会令人困惑,尤其是在时间戳的跟踪中。如果值得,请考虑使用包含对夏令时偏移量的引用的时区标识符来格式化您的日期字符串。大多数旧风格,即非奥尔森,时区名称包括这个(EST 与 EDT,GMT 与 BST 等)。这将足以消除歧义在紧要关头。这可能就是您所需要的,因为这种边界情况可能不值得过多地破坏显示器。如果您需要更多输出,您还可以使用 UTC 偏移量格式化时间戳,这将使转换在时间戳跟踪中非常明确。

          【讨论】:

            【解决方案6】:

            SQL Server 有一个新类型“datetimeoffset”可以很好地处理这个问题,因为您可以使用不同的偏移量来获得相同的时间。对于我的 SQL Server 2005 DB,我使用该类型的字符串文字 nvarchar(25),其形式为“YYYY-MM-DD hh:mm:ss-hh:mm”。

            为此,我创建了将这些字符串转换为正确时间的例程。

            【讨论】:

              【解决方案7】:

              我已经解决了新西兰时间的问题,如下所示:

              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
              

              【讨论】:

                猜你喜欢
                • 2016-02-06
                • 2012-04-07
                • 2011-07-26
                • 2014-08-11
                • 2014-06-01
                • 2018-02-04
                • 2020-01-13
                • 2014-12-14
                • 1970-01-01
                相关资源
                最近更新 更多