4

我正在使用 UTC 在数据库中存储数据和时间值。这些值在客户端或每个客户端时区转换为本地时间。我从MSDN 文章中了解了这些场景,其中显示来自 UTC 的时间似乎在夏令时造成了问题。

居住在美国东海岸的人输入了诸如“Oct 26, 2003 01:10:00 AM”之类的值。

1) 在这个特定的早晨,由于夏令时,凌晨 2:00,本地时钟重置为凌晨 1:00,创建 25 小时制的一天。由于在上午 1:00 和凌晨 2:00 之间的所有时钟时间值在那个特定的早晨出现两次——至少在美国和加拿大的大部分地区,计算机真的无法知道上午 1:10 的含义——切换前发生的事件,或夏令时切换后 10 分钟发生的事件。

2) 同样,问题发生在春天,在某个特定的早晨,没有凌晨 2:10 这样的时间。原因是在那个特定的早晨 2:00,本地时钟的时间突然变为凌晨 3:00。在这 23 小时的一天中,整个 2:00 小时都不会发生。

您是如何处理第 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);  
4

7 回答 7

3

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

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

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


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

于 2009-08-11T17:04:43.063 回答
3

您需要存储时间偏移量。

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

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")
于 2009-08-11T17:23:40.747 回答
1

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

按 UTC 对它们进行排序,并以本地时间显示。

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

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

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

于 2009-08-11T17:05:31.627 回答
1

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

于 2009-08-11T17:05:44.997 回答
1

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

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

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

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

于 2009-08-11T17:16:21.650 回答
0

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

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

于 2009-08-11T17:08:48.500 回答
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
于 2016-09-26T22:48:10.097 回答