我使用UTC在数据库中存储数据和时间值。这些值在客户端或每个客户端时区转换为本地时间。我从MSDN article踩到了这些场景,显示UTC的时间似乎在白天节省期间造成了问题。
生活在美国东海岸的人类型为“Oct 26,2003 01:10:00 AM”。
1)在这个特殊的早晨,由于夏令时,凌晨2点,当地时钟重置为凌晨1点,创造了25小时的一天。由于在上午1:00到凌晨2:00之间的所有时钟时间值在该特定早晨出现两次 - 至少在大多数美国和加拿大,计算机真的无法知道1:10 AM的意思是 - 在开关之前发生的那个,或在夏令时开关后10分钟发生的那个。
2)同样地,问题发生在春天,在特定的早晨,没有上午2:10这样的时间。原因是在那个特定的早晨2点,本地时钟的时间突然变为凌晨3点。整个2:00小时从未发生在这23小时的一天。
您是如何处理情况#1的,当您可能有4个交易,两个交换前和两个交换后的夏令时?如何向用户显示交易的时间,因为最后两个交易可能由于转移而显示比前两个交易更早的时间。有时,它可能证明不合逻辑,例如:在邮件链中。
添加:
要添加有关上下文的更多信息,在客户端(或通过Webservice与服务器通信的任何客户端应用程序)上运行的RIA应用程序(如Silverlight / Flash)允许用户选择交付时间或使用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);
这些情景是倡导使用DST的案例。只要以UTC格式存储和排序值,您显示的内容就无关紧要了。也就是说,如果您正确使用UTC,那么这些场景中出现的问题就会得到解决。
是的,看到这样的记录会很困惑:12:30,1:20,1:10,3:30但是如果按照UTC(真正发生的事情)的顺序排序,我认为这是正确的方式做到这一点。
因此,通过以UTC记录所有内容然后以UTC或相对时间(例如“17分钟前......”)显示所有内容,完全避免了这个问题。
如果您按照评论中的建议引用用户提供的日期/时间,我会给您带来一些坏消息:它很糟糕。我认为最好,最明显的解决方案是选择规则并运行它。如果你确实需要完美地处理它,你的UI需要扩展到迂腐地处理这种边缘情况,每年只发生1小时,然后只对非实时创建的交易(因为如果它们是真实的) - 时间,你知道DST等价物)。
您需要存储时间偏移量。
目前东海岸的时间是(往返格式)
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")
如何向用户显示交易的时间,因为最后两个交易可能由于转移而显示比前两个交易更早的时间。有时,它可能证明不合逻辑,例如:在邮件链中。
按UTC排序,并以当地时间显示。
因此用户可能会看到如下列表:
01:10:00
01:50:00
01:05:00
01:20:00
要么是这样,要么按UTC显示和排序。
如果你有人手动输入数据,祝你好运,除非他们在UTC时间输入数据。没有一种“正确”的方法可以解决这个问题。如果您正在处理非用户输入的日期,例如交易发生的时间,那么只需将这些日期存储为UTC并且生活良好。 :)
一般来说,您的问题分为两部分:
这两者应该类似地处理,但在某些方面有一些单独的解决方法。对于1,最简单的选择是找出您的企业是否真的需要一种明确的方式来指定该特定小时的时间。许多应用程序只是忽略它(你最后一次使用日期选择器的时候是什么时候?)并简单地假设任何一致的猜测算法就足够了。如果输入不存在的小时,您应该提供保护措施(即抛出错误)。
对于2,跳过的小时无关紧要,因为您的数据库是UTC。重复小时可能会令人困惑,特别是在时间戳的痕迹中。如果值得,请考虑使用时区标识符格式化日期字符串,该标识符包含对夏令时偏移量的引用。大多数旧式,即非Olson,时区名称包括此(EST与EDT,GMT与BST等)。这足以消除吝啬的歧义。这可能就是你所需要的,因为这个边界情况可能不值得过多地显示。如果您需要更多输出,您还可以使用UTC偏移格式化时间戳,这将使得时间戳跟踪中的转换非常明确。
SQL Server有一个新类型'datetimeoffset'可以很好地处理这个问题,因为你可以在不同的偏移量下使用相同的时间。对于我的SQL Server 2005 DB,我使用该类型的字符串文字,nvarchar(25)形式为“YYYY-MM-DD hh:mm:ss-hh:mm”。
为此,我创建了将这些字符串转换为正确时间的例程。
我已经解决了新西兰时间如下:
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