我在db中存储产品。所有日期(sql server datetime)都是UTC,以及我存储该产品的时区ID的日期。用户输入产品可用的日期"来自" "直到"在列表中。 所以我做了类似的事情:
// Convert user's datetime to UTC
var userEnteredDateTime = DateTime.Parse("11/11/2014 9:00:00");
// TimeZoneInfo id will be stored along with the UTC datetime
var tz = TimeZoneInfo.FindSystemTimeZoneById("FLE Standard Time");
// following produces: 9/11/2014 7:00:00 AM (winter time - 1h back)
var utcDateTime = TimeZoneInfo.ConvertTimeToUtc(userEnteredDateTime, tz);
并保存记录。让我们假设用户在8月1日这样做,而他的时区偏移到UTC仍然是+03:00,然而未来列表的保存日期具有正确的+02:00值,因为转换考虑在内"冬天"那个时期的时间。
问题是,如果我尝试转换该产品"来自"将会获得什么日期时间值。 "直到"例如,由于某些新规则导致过渡到冬季时间,因此时区仍然是+03:00而不是+02:00的日期到2014年11月11日的产品当地时区?
// Convert back
var userLocalTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, tz);
我将获得10AM或更正9AM,因为OS / .NET补丁会处理这个吗?
谢谢!
PS:TimeZoneInfo有ToSerializedString()方法,如果我宁愿存储这个值而不是时区id,这将保证通过UTC datetime + serialized timezoneinfo我将始终能够转换为用户的原始日期时间输入?
答案 0 :(得分:5)
在您描述的场景中,您将在上午10点到达。时区转换功能不会知道该值最初是在上午9:00输入的,因为您只保存了UTC时间7:00 AM。
这说明了一个建议"始终存储UTC"是有缺陷的。当您处理未来事件时,它并不总是有效。问题是政府经常改变他们对时区的看法。有时候他们会给出合理的通知(例如美国,2007年),但有时他们不会(例如埃及,2014年)。
当您从本地时间到UTC进行原始转换时,您有意决定相信时区规则不会更改。换句话说,您决定仅根据您当时所知的时区规则将事件分配到通用时间轴。
避免这种情况的方法很简单:未来事件应按当地时间安排。现在,我不是指你的电脑本地,而是用户本地的#34;所以你需要知道用户的时区,你还应该在某个地方存储时区的ID。
如果事件属于daylight saving time的前后或后退过渡,您还需要决定要执行的操作。这对于复发模式尤为重要。
但最终,您需要确定何时运行该事件。或者在您的情况下,您需要决定事件是否已经过去。有几种不同的方法可以实现这一目标:
选项1
您可以为每个本地时间计算相应的UTC值,并将其保存在单独的字段中。
在某个周期(每天,每周等),您可以从其本地值和您当前对时区规则的理解重新计算即将到来的UTC值。或者,如果您手动应用时区更新,则可以选择重新计算当时的所有内容。
选项2
您可以将值存储为DateTimeOffset
类型而不是DateTime
。它将包含原始本地时间,以及您在进入时根据时区规则计算的偏移量。
DateTimeOffset
值很容易被强制转换回UTC,因此它们往往能够很好地完成这项工作。您可以在DateTime vs DateTimeOffset。
与选项1一样,您可以定期或在时区数据更新后重新访问这些值,并调整偏移量以与新的时区数据保持一致。
这是我通常建议的,特别是如果您使用的数据库支持DateTimeOffset
类型,例如SQL Server或RavenDB。
选项3
您可以将值存储为本地DateTime
。
查询时,您将计算目标时区的当前时间并与该值进行比较。
DateTime now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, targetTZ);
bool passed = now >= eventTime;
此选项的缺点是,如果您有许多不同时区的活动,则可能需要进行大量查询。
您可能还会遇到接近DST过渡期的值的问题,因此请谨慎使用此方法。
我建议不要将时区本身序列化。如果时区已更改,则表示已更改。假装它不是一个好的解决方法。