日期从 angular(时区)到服务器(utc)然后 utc 到时区

Date from angular(timezone) to server (utc) then utc to Timezone

我无法将日期恢复到正确的时区。

首先在本地机器上一切正常,但在服务器上却不行:服务器托管在美国,客户端大部分在澳大利亚。

所以一个日期从angular app("12/23/2015 11:00:00 AM") 发送到服务器,服务器在数据库中存储一个日期作为utc,直到这一点一切正常(我检查过并且日期存储在正确的 utc 中)

book.StartDateTime = TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(book.StartDateTime.Value, DateTimeKind.Unspecified), ToolsHelper.OlsonTimeZoneToTimeZoneInfo(locationDetails.TimeZone)); // book.CreatedDate.Value.ToUniversalTime();

问题是:

当客户端请求存储在数据库中的一些日期时。存储在数据库中的日期是 return 返回给客户端这样的:

 bookview.StartDateTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(bookli.StartDateTime.Value, DateTimeKind.Utc), ToolsHelper.OlsonTimeZoneToTimeZoneInfo(deCompany.TimeZone));

我检查了一下,此时日期是“12/23/2015 11:00:00 AM”-> 转换就在服务器中(在服务器端我放了一个日志),

但在 angular 中显示为“12/23/2015 10:00:00 PM”

很明显,问题是 api 将日期传输给客户端的时间,也许是什么时候转换为 JSON

我尝试了不同的方法都没有用,我删除了 "DateTime.SpecifyKind",我将日期转换为字符串,然后又转换回日期时间格式,但似乎没有用。

我能做什么?

几件事:

  • 你的例子不完整,所以我只能推测一些地方。最好展示双方,包括如何加载和解析 Angular 中的数据,以及数据在网络上的样子。

  • 您不应该以 "12/23/2015 11:00:00 AM" 等特定于语言环境的格式来回发送日期。您可以在 UI 中使用它们,但它们不适用于网络(在您的 JSON 中)。相反,您应该使用 ISO8601/RFC3339,例如 "2015-12-23T11:00:00Z"。 (如果您正在使用 WebAPI,您可能已经在这样做了。)

  • 序列化为 ISO8601 格式的 DateTime 对象与 Kind 属性.

    中的关联 DateTimeKind 耦合
    • 如果 KindUtc,则 ISO8601 字符串将以 Z.
    • 结尾
    • 如果 KindLocal,则 ISO8601 字符串将以该时间戳的机器本地偏移量结尾,例如 -08:00.
    • 如果 KindUnspecified,那么 ISO8601 字符串将没有 Z 或偏移量 - 这意味着它不能明确表示特定的时刻。

    这最终是错误的原因。您正在将 DateTime 转换为另一个时区,这会留下 Unspecified 种类,然后在没有偏移的情况下进行序列化 - 因此在客户端(可能)在本地时区进行解释浏览器。

  • 更好的方法是使用 DateTimeOffset 而不是 DateTime。那么你就不用担心Kind了,偏移量一直存在。如果您将 bookview.StartDateTime 更改为 DateTimeOffset 类型,您可以执行以下操作来解决问题:

      DateTimeOffset dto = new DateTimeOffset(bookli.StartDateTime.Value, TimeSpan.Zero);
      bookView.StartDTO = TimeZoneInfo.ConvertTime(dto, ToolsHelper.OlsonTimeZoneToTimeZoneInfo(deCompany.TimeZone));
    

    这将确保偏移量保留在数据中。

  • 在客户端,要特别注意ISO字符串是如何被解析的。如果它被加载到 Date 对象中,那么它确实会被转换为客户端的时区。相反,您可以查看 moment.js 以了解客户端时间格式。特别是,使用 moment.parseZone 将值保持在它所呈现的相同偏移量中。例如:

      var s = moment.parseZone("2015-12-31T11:00:00+00:00").format("L LT"); // "12/31/2015 11:00 AM"
    
  • 在注释代码中,您还显示了对 DateTime.ToUniversalTime 的调用 - 对此要非常小心。如果源种类为 Unspecified,则将其视为 Local。因此,计算机的本地时区反映在转换值中。最好完全避免使用 ToUniversalTimeToLocalTime。仅使用 TimeZoneInfo.

    上的转换方法
  • ToolsHelper.OlsonTimeZoneToTimeZoneInfo 也不是我们所知道的。但我假设它执行类似于 this one. However, if you're working with Olson time zones anyway, the better approach would be to not use TimeZoneInfo at all. Instead, use Noda Time 的 CLDR 映射,它对 tzdb 时区提供本机支持。