指定datetime的时区而不更改值

时间:2021-05-20 03:35:57

I'm wondering how to go about changing the timezone of a DateTime object without actually changing the value. Here is the background...

我想知道如何在不实际更改值的情况下更改DateTime对象的时区。这是背景......

I have an ASP.NET MVC site hosted on AppHarbor and the server has its time set to UTC. When I submit a form from my site containing a datetime value, say 9/17/2013 4:00am, it comes up to the server with that value. However then when I do:

我在AppHarbor上托管了一个ASP.NET MVC站点,服务器的时间设置为UTC。当我从我的网站提交包含日期时间值的表单时,例如2013年9月17日上午4:00,它会使用该值来到服务器。然而,当我这样做时:

public ActionResult Save(Entity entity)
{
    entity.Date = entity.Date.ToUniversalTime();
    EntityService.Save(entity);
}

...it incorrectly keeps it as the same time (4am) because the server is already on UTC time. So after converting to UTC and saving to the database, the database value is 2013-09-17 04:00:00.000, when really it should be 2013-09-17 08:00:00.000, as the browser is on EST. I was hoping after form submission and passing the values to the controller action, it would set the DateTime's timezone to the browser's (EST) rather than the server host's (UTC), however that didn't happen.

...它错误地将其保持为同一时间(凌晨4点),因为服务器已经处于UTC时间。因此,在转换为UTC并保存到数据库后,数据库值为2013-09-17 04:00:00.000,实际应该是2013-09-17 08:00:00.000,因为浏览器在EST上。我希望在表单提交并将值传递给控制器​​操作后,它会将DateTime的时区设置为浏览器(EST)而不是服务器主机(UTC),但这并没有发生。

In any case, now I'm stuck with a DateTime object that contains the correct date/time value but the wrong time zone, so I can't correctly convert it to UTC. I store the time zone of each user, so if there was a way to set the time zone for the DateTime object without actually changing the value (I can't do TimeZoneInfo.ConvertTime because it'll change the value) then I could have the correct date/time value AND the correct time zone, then I could safely do date.ToUniversalTime. That being said, I've only seen how to change the KIND of a DateTime, not the TimeZone, without changing its actual value.

在任何情况下,现在我都遇到了一个包含正确日期/时间值但时区错误的DateTime对象,所以我无法正确地将其转换为UTC。我存储每个用户的时区,所以如果有办法设置DateTime对象的时区而不实际更改值(我不能做TimeZoneInfo.ConvertTime,因为它会改变值)然后我可以有正确的日期/时间值和正确的时区,然后我可以安全地做date.ToUniversalTime。话虽这么说,我只看到如何在不改变其实际值的情况下更改DateTime的KIND而不是TimeZone。

Any ideas?

有任何想法吗?

1 个解决方案

#1


12  

Actually, it is doing exactly what you asked it to do. From the point of view of the server, you passed it an "unspecified" DateTime. In other words, just year, month day, etc., withoutout any context. If you look at the .Kind property, you will see that it is indeed DateTimeKind.Unspecified.

实际上,它完全按照你的要求去做。从服务器的角度来看,您传递了一个“未指定的”DateTime。换句话说,只是年,月等,没有任何背景。如果你查看.Kind属性,你会发现它确实是DateTimeKind.Unspecified。

When you call .ToUniversalTime() on an unspecified kind of DateTime, .NET will assume the context of the local time zone of the computer that the code is running on. You can read more about this in the documentation here. Since your server is set to UTC then regardless of the input kind, all three kinds will yield the same result. Basically, it's a no-op.

当您在未指定类型的DateTime上调用.ToUniversalTime()时,.NET将假定运行代码的计算机的本地时区的上下文。您可以在此处的文档中阅读更多相关信息。由于您的服务器设置为UTC,因此无论输入类型如何,这三种类型都将产生相同的结果。基本上,这是一个无操作。

Now, you said that you wanted the time to reflect the user's time zone. Unfortunately, that is information that the server doesn't have. There's no magic HTTP header that carries time zone details.

现在,您说您希望有时间反映用户的时区。不幸的是,这是服务器没有的信息。没有神奇的HTTP标头带有时区细节。

There are ways to achieve this effect though, and you have some options.

有一些方法可以实现这种效果,你有一些选择。

JavaScript Method

JavaScript方法

This is probably the easiest option, but it does require JavaScript.

这可能是最简单的选择,但它确实需要JavaScript。

  • Take the local date and time input from your user and parse it into a JavaScript Date class.
    • For easier parsing, you might consider using a moment instead, from the moment.js library.
    • 为了便于解析,您可以考虑使用moment.js库中的一个时刻。
  • 从用户输入本地日期和时间输入并将其解析为JavaScript Date类。为了便于解析,您可以考虑使用moment.js库中的一个时刻。
  • Get the UTC date and time from the Date or moment, and pass it to your server.
    • This is relatively easy in JavaScript, so I'll spare you the details.
    • 这在JavaScript中相对容易,所以我将为您提供详细信息。
    • There are many formats you can pass it, but the preferred way is as an ISO8601 timestamp. Example: 2013-09-17T08:00:00.000Z
    • 您可以传递许多格式,但首选方式是ISO8601时间戳。示例:2013-09-17T08:00:00.000Z
  • 从日期或时刻获取UTC日期和时间,并将其传递给您的服务器。这在JavaScript中相对容易,所以我将为您提供详细信息。您可以传递许多格式,但首选方式是ISO8601时间戳。示例:2013-09-17T08:00:00.000Z
  • Don't forget the Z at the end. That designates that the time is in UTC.
  • 不要忘记最后的Z.这表示时间是UTC。
  • Since it is passed to you as UTC, you can just store it without any conversion.
  • 由于它以UTC格式传递给您,因此您可以在不进行任何转换的情况下存储它。
  • When retrieving it, you again pass UTC to the browser, load it into a Date or a moment with JavaScript, and then emit the local date and time.
  • 检索它时,再次将UTC传递给浏览器,使用JavaScript将其加载到日期或片刻,然后发出本地日期和时间。
  • I highly recommend you try moment.js if you take this approach. It can be done without it, but that can be much more complicated and error prone.
  • 如果采取这种方法,我强烈建议你试试moment.js。它可以在没有它的情况下完成,但这可能更复杂且容易出错。

.NET Method using Windows Time Zones

使用Windows时区的.NET方法

If you aren't going to invoke JavaScript, then you will need to ask the user for their time zone. This can work well in a larger application, such as on the user's profile page.

如果您不打算调用JavaScript,则需要询问用户他们的时区。这可以在更大的应用程序中很好地工作,例如在用户的个人资料页面上。

  • Use TimeZoneInfo.GetSystemTimeZones to build a drop-down list.
    • For each TimeZoneInfo item in the list, use the .Id for the value, and the .DisplayName for the text.
    • 对于列表中的每个TimeZoneInfo项,使用.Id作为值,使用.DisplayName作为文本。
  • 使用TimeZoneInfo.GetSystemTimeZones构建下拉列表。对于列表中的每个TimeZoneInfo项,使用.Id作为值,使用.DisplayName作为文本。
  • Then you can use this value when you want to convert the time.

    然后,当您想要转换时间时,可以使用此值。

    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
    DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
    
  • This will work with Windows time zones, which are a Microsoft creation, and have some drawbacks. You can read about a few of their deficiencies in the timezone tag wiki.

    这将适用于Windows时区,这是Microsoft创建的,并且有一些缺点。您可以在时区标签wiki中了解它们的一些缺陷。

.NET Method using IANA Time Zones

使用IANA时区的.NET方法

If you want to use the more standard IANA time zones, such as America/New_York or Europe/London, then you can use a library like Noda Time. It offers a much better API for working with date and time than the built-in framework. There's a bit of a learning curve, but if you're doing anything complicated it is well worth the effort. As an example:

如果您想使用更标准的IANA时区,例如America / New_York或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();


About Daylight Saving Time

Regardless of which of these three approaches you take, you will have to deal with the problems created by Daylight Saving Time. Each of these samples shows a "lenient" approach, where if the local time you specify is ambiguous or invalid, that some rule is followed so you still get some valid moment in time. You can see this directly with the Noda Time approach when I called AtLeniently. But it occurs with the other ones also - it's just implicit. In JavaScript, the rules can vary per browser, so don't expect consistent results.

无论您采用这三种方法中的哪一种,都必须处理夏令时产生的问题。这些示例中的每一个都显示“宽松”方法,如果您指定的本地时间不明确或无效,则遵循某些规则,以便您仍然获得一些有效的时刻。当我打电话给AtLeniently时,您可以直接使用Noda Time方法看到这一点。但它也与其他的一起发生 - 它只是隐含的。在JavaScript中,规则可能因浏览器而异,因此不要期望一致的结果。

Depending on what kind of data you're collecting, you may decide it's perfectly acceptable to make this kind of assumption. But in many cases it's not appropriate to assume. In that case, you may need to either alert your user that the input time is invalid, or ask them which of two ambiguous times they meant.

根据您收集的数据类型,您可能会认为做出这种假设是完全可以接受的。但在许多情况下,这是不恰当的假设。在这种情况下,您可能需要提醒您的用户输入时间无效,或者询问他们意味着两个不明确的时间。

In .Net, you can check for this with TimeZoneInfo.IsInvalidTime and TimeZoneInfo.IsAmbiguousTime.

在.Net中,您可以使用TimeZoneInfo.IsInvalidTime和TimeZoneInfo.IsAmbiguousTime来检查。

For an example of how daylight saving time works, see here. In the "spring-forward" transition, a time during the transition is invalid. In the "fall-back" transition, a time during the transition is ambiguous - that is, it could have happened either before or after the transition.

有关夏令时如何工作的示例,请参见此处。在“前进”转换中,转换期间的时间无效。在“后退”过渡期间,过渡期间的时间是模糊的 - 也就是说,它可能在过渡之前或之后发生。

#1


12  

Actually, it is doing exactly what you asked it to do. From the point of view of the server, you passed it an "unspecified" DateTime. In other words, just year, month day, etc., withoutout any context. If you look at the .Kind property, you will see that it is indeed DateTimeKind.Unspecified.

实际上,它完全按照你的要求去做。从服务器的角度来看,您传递了一个“未指定的”DateTime。换句话说,只是年,月等,没有任何背景。如果你查看.Kind属性,你会发现它确实是DateTimeKind.Unspecified。

When you call .ToUniversalTime() on an unspecified kind of DateTime, .NET will assume the context of the local time zone of the computer that the code is running on. You can read more about this in the documentation here. Since your server is set to UTC then regardless of the input kind, all three kinds will yield the same result. Basically, it's a no-op.

当您在未指定类型的DateTime上调用.ToUniversalTime()时,.NET将假定运行代码的计算机的本地时区的上下文。您可以在此处的文档中阅读更多相关信息。由于您的服务器设置为UTC,因此无论输入类型如何,这三种类型都将产生相同的结果。基本上,这是一个无操作。

Now, you said that you wanted the time to reflect the user's time zone. Unfortunately, that is information that the server doesn't have. There's no magic HTTP header that carries time zone details.

现在,您说您希望有时间反映用户的时区。不幸的是,这是服务器没有的信息。没有神奇的HTTP标头带有时区细节。

There are ways to achieve this effect though, and you have some options.

有一些方法可以实现这种效果,你有一些选择。

JavaScript Method

JavaScript方法

This is probably the easiest option, but it does require JavaScript.

这可能是最简单的选择,但它确实需要JavaScript。

  • Take the local date and time input from your user and parse it into a JavaScript Date class.
    • For easier parsing, you might consider using a moment instead, from the moment.js library.
    • 为了便于解析,您可以考虑使用moment.js库中的一个时刻。
  • 从用户输入本地日期和时间输入并将其解析为JavaScript Date类。为了便于解析,您可以考虑使用moment.js库中的一个时刻。
  • Get the UTC date and time from the Date or moment, and pass it to your server.
    • This is relatively easy in JavaScript, so I'll spare you the details.
    • 这在JavaScript中相对容易,所以我将为您提供详细信息。
    • There are many formats you can pass it, but the preferred way is as an ISO8601 timestamp. Example: 2013-09-17T08:00:00.000Z
    • 您可以传递许多格式,但首选方式是ISO8601时间戳。示例:2013-09-17T08:00:00.000Z
  • 从日期或时刻获取UTC日期和时间,并将其传递给您的服务器。这在JavaScript中相对容易,所以我将为您提供详细信息。您可以传递许多格式,但首选方式是ISO8601时间戳。示例:2013-09-17T08:00:00.000Z
  • Don't forget the Z at the end. That designates that the time is in UTC.
  • 不要忘记最后的Z.这表示时间是UTC。
  • Since it is passed to you as UTC, you can just store it without any conversion.
  • 由于它以UTC格式传递给您,因此您可以在不进行任何转换的情况下存储它。
  • When retrieving it, you again pass UTC to the browser, load it into a Date or a moment with JavaScript, and then emit the local date and time.
  • 检索它时,再次将UTC传递给浏览器,使用JavaScript将其加载到日期或片刻,然后发出本地日期和时间。
  • I highly recommend you try moment.js if you take this approach. It can be done without it, but that can be much more complicated and error prone.
  • 如果采取这种方法,我强烈建议你试试moment.js。它可以在没有它的情况下完成,但这可能更复杂且容易出错。

.NET Method using Windows Time Zones

使用Windows时区的.NET方法

If you aren't going to invoke JavaScript, then you will need to ask the user for their time zone. This can work well in a larger application, such as on the user's profile page.

如果您不打算调用JavaScript,则需要询问用户他们的时区。这可以在更大的应用程序中很好地工作,例如在用户的个人资料页面上。

  • Use TimeZoneInfo.GetSystemTimeZones to build a drop-down list.
    • For each TimeZoneInfo item in the list, use the .Id for the value, and the .DisplayName for the text.
    • 对于列表中的每个TimeZoneInfo项,使用.Id作为值,使用.DisplayName作为文本。
  • 使用TimeZoneInfo.GetSystemTimeZones构建下拉列表。对于列表中的每个TimeZoneInfo项,使用.Id作为值,使用.DisplayName作为文本。
  • Then you can use this value when you want to convert the time.

    然后,当您想要转换时间时,可以使用此值。

    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
    DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
    
  • This will work with Windows time zones, which are a Microsoft creation, and have some drawbacks. You can read about a few of their deficiencies in the timezone tag wiki.

    这将适用于Windows时区,这是Microsoft创建的,并且有一些缺点。您可以在时区标签wiki中了解它们的一些缺陷。

.NET Method using IANA Time Zones

使用IANA时区的.NET方法

If you want to use the more standard IANA time zones, such as America/New_York or Europe/London, then you can use a library like Noda Time. It offers a much better API for working with date and time than the built-in framework. There's a bit of a learning curve, but if you're doing anything complicated it is well worth the effort. As an example:

如果您想使用更标准的IANA时区,例如America / New_York或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();


About Daylight Saving Time

Regardless of which of these three approaches you take, you will have to deal with the problems created by Daylight Saving Time. Each of these samples shows a "lenient" approach, where if the local time you specify is ambiguous or invalid, that some rule is followed so you still get some valid moment in time. You can see this directly with the Noda Time approach when I called AtLeniently. But it occurs with the other ones also - it's just implicit. In JavaScript, the rules can vary per browser, so don't expect consistent results.

无论您采用这三种方法中的哪一种,都必须处理夏令时产生的问题。这些示例中的每一个都显示“宽松”方法,如果您指定的本地时间不明确或无效,则遵循某些规则,以便您仍然获得一些有效的时刻。当我打电话给AtLeniently时,您可以直接使用Noda Time方法看到这一点。但它也与其他的一起发生 - 它只是隐含的。在JavaScript中,规则可能因浏览器而异,因此不要期望一致的结果。

Depending on what kind of data you're collecting, you may decide it's perfectly acceptable to make this kind of assumption. But in many cases it's not appropriate to assume. In that case, you may need to either alert your user that the input time is invalid, or ask them which of two ambiguous times they meant.

根据您收集的数据类型,您可能会认为做出这种假设是完全可以接受的。但在许多情况下,这是不恰当的假设。在这种情况下,您可能需要提醒您的用户输入时间无效,或者询问他们意味着两个不明确的时间。

In .Net, you can check for this with TimeZoneInfo.IsInvalidTime and TimeZoneInfo.IsAmbiguousTime.

在.Net中,您可以使用TimeZoneInfo.IsInvalidTime和TimeZoneInfo.IsAmbiguousTime来检查。

For an example of how daylight saving time works, see here. In the "spring-forward" transition, a time during the transition is invalid. In the "fall-back" transition, a time during the transition is ambiguous - that is, it could have happened either before or after the transition.

有关夏令时如何工作的示例,请参见此处。在“前进”转换中,转换期间的时间无效。在“后退”过渡期间,过渡期间的时间是模糊的 - 也就是说,它可能在过渡之前或之后发生。