如何使用偏移量转换时间跨度

How to convert timespan using offset

我有 MVC 网络应用程序。我将 UTC 时间存储在数据库中。 (不是日期时间,只是一个时间)。在 C# 中,当我从数据库中检索这个时间时,我得到了时间跨度对象。我也有在几分钟内可用的偏移量。例如。

double offset = 600;

如何使用此偏移量将时间跨度转换为本地日期时间。

注意我不想使用 DateTime.ToLocalTime () 方法,因为那样会使用服务器的时区。

更新1
我正在使用 Javascript new Date().getTimezoneOffset() 方法来获取客户端的偏移量,并且我在服务器上存储了偏移量值。然后我还有下拉列表,显示时间为 12:00 AM、12.30 AM、1:00 AM 等。下拉列表绑定到 属性 SelectedDateTime 类型的模型 DateTime。思路是将用户 selected 时间转换为 UTC,然后根据偏移量将 UTC 转换为本地时间。所以假设我有偏移量 300 minitues 那将是 300/60 = 5 hours

double offset = 5.00; // this is available on the server

当用户 selects 在下拉列表中显示时间时,我在服务器上获取了一个日期时间对象,忽略了我想将 UTC 时间存储到数据库中的日期部分。这就是我转换为 UTC 时间的方式。

TimeSpan utcTime = SelectedDateTime.AddHours(offset).TimeOfday;

我将这个 utcTime 存储到数据库中。现在我想将 UTC 时间跨度转换为客户端的日期时间。 我假设我现在有减去偏移量

var newLocalTimeSpan = utcTime.Subtract(TimeSpan.FromHours(offset));
var newLocalDateTime = new DateTime(newLocalTimeSpan.Ticks, DateTimeKind.Local);

但是这会引发错误:

Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks.\r\nParameter name: ticks

例如,offest 5 小时,如果用户 selects 8:00 PM 那么它将被转换为 UTC 并将作为 01:00:00.0000000 存储在数据库中。当我从数据库中检索 UTC 值时,它的“1:00:00 AM”。然后我从这个 TimeSpan 中减去 5 小时,它现在等于“-4”,如果我将 Ticks 传递给 DateTime..我会得到上述错误。

注意事项: 如果您好奇为什么模型 属性 是 DateTime 而不是 TimeSpan 那是因为我正在使用需要 DateTime 类型的 Kendo TimePicker。

更新 2
非常感谢大家的帮助。我已经阅读了@Matt Johnson posted 的所有文章,看起来我不应该使用偏移来计算 UTC 时间。主要是因为白天节省时间。但我应该使用时区。所以我在这里有 3 个选项来查找客户的时区:

1> 使用JavaScript检测时区
在 JavaScript 我可以做 new Date().toString() returns 日期时间作为 Sun May 22 2016 02:12:36 GMT-0500 (Central Daylight Time) 然后我可以解析字符串以获得“中央夏令时”和 post 它到服务器。但是在服务器上,对于 .net,“中央夏令时”不是有效的 windows 时区 ID。
问题
这是正确的方法吗? JavaScript return 是 IANA 区域 ID 吗?它总是 return IANA 区域 ID 吗?

如果 JavaScript 是 returning IANA id 那么我可以使用 Matt 的文章 here 来获取 windows 时区 id

2> 使用http://momentjs.com/检测客户的时区
问题
momentjs returns IANA zone id?
如果 momentjs return IANA zone id 那么我可以使用上面 Matt 的文章来获取 windows zone id。我不喜欢这种方法的原因之一是因为我必须使用 2 个第三方库 momentjsNoda Time

3> 使用 TimeZoneInfo.GetSystemTimeZones() 为用户提供下拉列表并让用户 select 设置时区。
用户将 select 时间和时区,然后在服务器上我将使用 selected 时区将其转换为 UTC 并将其保存在数据库中。但是我必须在其他一些页面上显示那个时间,所以我再次需要时区。这意味着我必须将下拉列表放在 UI 这样一个随时可用的位置。喜欢顶部菜单。

(我当然可以将时区与时间一起保存到数据库中,但是如果用户前往其他地方,他仍然会看到最初 selected 时区的时间。我不想要)

这些方法正确吗?我错过了什么吗?

问题
假设我使用上述方法之一实现时区 selection,并且我在某些变量的服务器上具有正确的客户端时区和 windows 时区 ID。
现在假设用户 selects 6:00 PM(中央夏令时,UTC -5)将转换为 UTC 为 23:00:00。只要我们处于中部夏令时,从 UTC 到本地的转换就会显示 6:00 PM。一旦我们进入 Central Standard Time,即 UTC -6,转换是否仍会显示 6:00 PM 或 5:00 PM?
我打算使用 TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) 方法将 UTC 转换为本地

您需要使用当前年、月和日将 TimeSpan 转换为 DateTime。如果不这样做就从 TimeSpan 中减去,可能会导致无法获得日期。
此外,我注意到在您的更新中您将结果保留在 DateTime 中,所以我也这样做了。 如您的问题所述,如果 UTC 时间是 1:00 AM,此代码会向您显示时间。

        double offset = 5.00;
        TimeSpan utcTime = new TimeSpan(1,0,0); //setting manually to your representation of 1 am.
        DateTime newLocalDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, utcTime.Hours, utcTime.Minutes, utcTime.Seconds);
        newLocalDateTime = newLocalDateTime.Subtract(TimeSpan.FromHours(offset));

一般来说,只有两种可行的方法:

  1. 仅将 UTC 日期和时间传递给客户端,并使用 JavaScript.

    在浏览器中将所有时间转换为本地时间
    • 当您不关心实际时区是什么,而只是希望它与浏览器的本地时间相匹配时,请使用此方法。
    • Date 对象可以做到这一点,但您可能会发现使用 moment.js 等库更容易,它可以让您更好地控制输出格式等。
  2. 在服务器端将时区(不仅仅是偏移量)应用到 UTC 日期和时间,以生成正确的本地时间值。

    • 当时区影响整个应用程序并且需要在服务器端业务逻辑中知道时区时使用此方法。
    • 您可以尝试使用jsTimeZoneDetect or moment.tz.guess() in moment-timezone猜测用户的时区。但是,这只是一个猜测,它始终是一个 IANA 时区 ID(例如 America/Los_Angeles)。
    • 从列表中询问用户他们的时区是个好主意。通常人们会把它放在用户设置或个人资料页面上。您可以使用之前的猜测从列表中选择一个默认值。
    • 如果您在客户端使用 IANA 时区,您确实需要在服务器上使用 Noda Time
    • 有些应用程序选择列出 Windows 时区,这是一种更简单的方法,因为您可以获得 TimeZoneInfo class 中的所有内容。但是,请认识到这种方法存在局限性,包括:
      • 本地化问题,因为除了与 操作系统的 默认语言匹配的显示名称字符串之外,您无法轻易获得显示名称字符串,而不是 .NET 的全球化和本地化功能。
      • 可维护性问题,因为您将控制权交给操作系统以保持时区数据更新。这可能看起来更方便,但你可能会发现在跟上 short-notice time zone changes 时你的手被束缚了。当您无法控制如何或何时将更新应用于 OS(例如 Microsoft Azure 应用服务)时,这尤其成问题。
      • 兼容性问题,因为 Windows 时区在 Windows 之外通常无法识别。如果您曾在 API 中公开用户的时区设置,您可能会遇到来自其他平台的呼叫者的翻译问题。

现在,进入你的具体要点:

I am using javascript new Date().getTimezoneOffset() method to get the client's offset...

这为您提供了客户端的 当前 偏移量。您无法保证申请任意日期和时间是正确的时区。

如果想在 C# 中对 UTC DateTime 应用固定偏移量,最好的方法是使用 DateTimeOffset.

DateTime utc = new DateTime(2016, 12, 31, 0, 0, 0, DateTimeKind.Utc);
DateTimeOffset dto = new DateTimeOffset(utc); // DateTimeKind matters here
TimeSpan offset = TimeSpan.FromMinutes(-300); // The offset is inverse of JavaScript's
DateTimeOffset result = dto.ToOffset(offset);

但请注意,这仅适用于 固定 时区偏移。对于真正的 时区 ,如果您使用 Windows 时区,您将使用 TimeZoneInfo class,或者您将使用 NodaTime 的 DateTimeZone class IANA 时区。

In JavaScript I can do new Date().toString() which returns date time as Sun May 22 2016 02:12:36 GMT-0500 (Central Daylight Time) I can then parse the string to get "Central Daylight Time" and post it to the server.

不,不推荐这种方法,原因如下:

  • 无法保证您将从 JavaScript 的 toString 函数中获得任何特定格式的输出。结果是特定于实现的,并且会因浏览器和平台而异。

  • 它们通常用于 显示 目的。当 DST 生效时,它们将显示夏令时名称,当标准时间生效时,它们将显示标准名称。

  • 他们通常本地化用户的语言,英语、法语、中文等

唯一可以return用户时区的本地API是:

Intl.DateTimeFormat().resolvedOptions().timeZone

这是 ECMAScript Internationalization API 的一部分。不幸的是,它目前只适用于少数浏览器。 jsTimeZoneDetect 和 moment.tz.guess() 都将使用此 API 如果可用,则将退回到他们自己的猜测逻辑。

Assume that i implement timezone selection using one of the approach above and i have correct client's time zone with windows timezone id on server in some variable. Now lets say user selects 6:00 PM (Central Daylight Time , UTC -5) which will convert to UTC as 23:00:00. As long we are in Central Daylight Time the conversion from UTC to local will show 6:00 PM. Once we go into Central Standard Time which is UTC -6 Will the conversion still show 6:00 PM or 5:00 PM? I am planning to use TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) method for converting UTC to Local

正如您之前所说,"Central Daylight Time" 不是有效的 Windows 时区标识符。您的用户不会选择那个。您将显示从 TimeZoneInfo.GetSystemTimeZones() 生成的列表,向用户显示 DisplayName,并使用 Id 作为值。 Id 将是 "Central Standard Time",这确实是美国中部时间的正确标识符,包括 CST 和 CDT - 尽管字符串中有单词 "Standard"。