实际上,它正在做你要求它做的事情。从服务器的角度来看,您传递给它一个 "unspecified" DateTime
。换句话说,只是年、月日等,没有任何上下文。如果您查看该.Kind
属性,您会发现它确实是DateTimeKind.Unspecified
.
当您调用.ToUniversalTime()
未指定类型的DateTime
.NET 时,.NET 将假定运行代码的计算机的本地时区的上下文。您可以在此处的文档中阅读有关此内容的更多信息。由于您的服务器设置为 UTC,因此无论输入类型如何,所有三种类型都会产生相同的结果。基本上,这是一个无操作。
现在,您说您希望时间反映用户的时区。不幸的是,这是服务器没有的信息。没有带有时区详细信息的神奇 HTTP 标头。
不过,有一些方法可以实现这种效果,并且您有一些选择。
JavaScript 方法
这可能是最简单的选择,但它确实需要 JavaScript。
- 获取用户输入的本地日期和时间,并将其解析为 JavaScript
Date
类。
Date
从or获取 UTC 日期和时间moment
,并将其传递给您的服务器。
- 这在 JavaScript 中相对容易,所以我将不详细介绍。
- 您可以传递多种格式,但首选方式是 ISO8601 时间戳。例子:
2013-09-17T08:00:00.000Z
- 不要忘记
Z
最后的。这表示时间是 UTC。
- 由于它是作为 UTC 传递给您的,因此您可以直接存储它而无需任何转换。
- 检索它时,您再次将 UTC 传递给浏览器,使用 JavaScript 将其加载到 a
Date
或 amoment
中,然后发出本地日期和时间。
- 如果您采用这种方法,我强烈建议您尝试moment.js 。它可以在没有它的情况下完成,但这可能会更加复杂且容易出错。
.NET 方法使用 Windows 时区
如果您不打算调用 JavaScript,那么您需要询问用户他们的时区。这可以在较大的应用程序中很好地工作,例如在用户的个人资料页面上。
- 用于
TimeZoneInfo.GetSystemTimeZones
构建下拉列表。
- 对于列表中的每个
TimeZoneInfo
项目,使用.Id
代表值,使用.DisplayName
代表文本。
然后,当您要转换时间时,可以使用此值。
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
这将适用于 Windows 时区,这是 Microsoft 创建的,并且有一些缺点。您可以在时区标签 wiki中了解他们的一些不足之处。
使用 IANA 时区的 .NET 方法
如果您想使用更标准的 IANA 时区,例如America/New_York
or Europe/London
,那么您可以使用像Noda Time这样的库。它提供了比内置框架更好的处理日期和时间的 API。有一点学习曲线,但如果你正在做任何复杂的事情,那么努力是值得的。举个例子:
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
var pattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = pattern.Parse("2013-09-17 04:00:00").Value;
ZonedDateTime zdt = tz.AtLeniently(dt);
Instant utc = zdt.ToInstant();
关于夏令时
无论您采用这三种方法中的哪一种,您都必须处理夏令时带来的问题。这些示例中的每一个都显示了一种“宽松”的方法,如果您指定的本地时间不明确或无效,则遵循某些规则,因此您仍然可以获得一些有效的时间。当我打电话时,您可以使用 Noda Time 方法直接看到这一点AtLeniently
。但它也与其他人一起发生 - 它只是隐含的。在 JavaScript 中,规则可能因浏览器而异,因此不要期望结果一致。
根据您收集的数据类型,您可能会决定做出这种假设是完全可以接受的。但在许多情况下,假设是不合适的。在这种情况下,您可能需要提醒用户输入时间无效,或者询问他们所指的两个模棱两可的时间中的哪一个。
在 .Net 中,您可以使用TimeZoneInfo.IsInvalidTime
和进行检查TimeZoneInfo.IsAmbiguousTime
。
有关夏令时如何工作的示例,请参见此处。在“spring-forward”过渡中,过渡期间的时间是无效的。在“后备”过渡中,过渡期间的时间是不明确的——也就是说,它可能发生在过渡之前或之后。