替换时区

Replace TimeZone

我们正在尝试构建基本的事件日历功能,允许用户创建事件并在给定的月、日、年、小时和分钟以及时区指定开始时间(System.TimeZoneInfo.Id). CMS 系统根据我们服务器的位置生成结果 System.DateTime,比方说 TimeZoneInfo.Id 山地标准时间 。 CMS 的日期选择器组件不提供指定时区的选项。但是我们确实可以控制 SQL 日期时间精度,默认设置为 7.

为了在 .ics/ical 中填充 start/end 次,DateTime 被格式化为 yyyyMMddTHHmmssZ。使用这种格式,它使 2018 年 5 月 25 日 7:00PM (20180508T192840Z) 始终看起来像服务器的山地标准时间 (MST),而不是选定的东部标准时间 (EST) 中的 2018 年 5 月 25 日 7:00PM ).

如何 "replace" 在不更改 year/month/day/hour/minute 的情况下生成 DateTime 的时区 DateTimeDateTimeOffsetTimeZoneInfoNodaTime,甚至 string 函数格式化为 yyyyMMddTHHmmssZ?

以下:

TimeZoneInfo destinationTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var converted = TimeZoneInfo.ConvertTime(dateTime1, destinationTimeZone);

或:

LocalDateTime fromLocal = LocalDateTime.FromDateTime(dateTime1);
DateTimeZone fromZone = DateTimeZoneProviders.Tzdb["America/Denver"];
ZonedDateTime fromZoned = fromLocal.InZoneLeniently(fromZone);

DateTimeZone toZone = DateTimeZoneProviders.Tzdb["America/Chicago"];
ZonedDateTime toZoned = fromZoned.WithZone(toZone);
LocalDateTime toLocal = toZoned.LocalDateTime;
var result = toLocal.ToDateTimeUnspecified();

创建一个新的 DateTime,小时数从 CST 调整为 EST,这不起作用,因为目标是 DateTime 具有原始小时值但 TimeZoneInfo.Id 东部标准时间.

DateTime构造函数好像没有指定TimeZoneInfo的构造函数,只有DateTimeKind.

如何使用某些甚至从 DateTime.Now 创建的 DateTime 来做到这一点?

DateTime 类型不识别时区,它所知道的有关时区的所有信息都是 DateTimeKind,可以是 LocalUtcUnspecified.包含在字符串表示中的区域信息将基于 Kind 值和服务器时区。

您应该使用 DateTimeOffset 作为您的场景,它将日期时间和时区信息存储在一个值中:

var dateTime = DateTime.Now; /*your date time here*/
var destinationTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var zonedDateTime = new DateTimeOffset(DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified), destinationTimeZone.BaseUtcOffset);
var dateTimeStr = zonedDateTime.ToString("o"/*your format goes here*/);

几件事:

  • 您的格式说明符末尾包含一个 Z。这被 .Net 的字符串格式视为 字符文字 ,因为它不是有效的 datetime formatting specifier。请注意,格式化标记区分大小写。作为文字,它只是被复制到输出 - 就像 T 一样。因此,您生成的这个字符串总是会被解析它的任何东西解释为 UTC,因为这就是 Z 在 ISO 8601 标准中的含义。这最终是您所面临问题的根本原因。

    如果您想让它反映一个不明确的本地时间(因为时区可能在您的 .ics 中的其他地方?),则完全省略 Z。但是,如果您打算包括时区偏移量,那么您可以将 K 说明符用于 DateTime 值,或者可能将 zzz 说明符与 DateTimeOffset 值结合使用 - 取决于根据您的具体需求。

  • 正如其他人指出的那样,DateTime 不是时区感知的,但也要注意 DateTimeOffset 也不是,因为它只跟踪与 UTC 的偏移量而不是特定的时区。例如,它可以跟踪 -07:00,但不能告诉您它处于山地时间。这就是为什么 Noda Time 有它的 ZonedDateTime 类型。 .Net 本身没有任何此类内置类型。

  • 在您的代码中,而不是在对 TimeZoneInfo.ConvertTime 的调用中,将考虑 dateTime1 变量的 .Kind。如果它是 DateTimeKind.Utc,那么结果将是确定性的。但如果它是 DateTimeKind.Unspecified,或 DateTimeKind.Local,那么它将被视为就好像是根据本地计算机的时区 - 即 server 时区在你的情况下。

  • 请注意,无论服务器的时区设置如何, 最好以相同的方式编写代码。这通常意味着避免使用 DateTimeKind.Local,例如 DateTime.NowTimeZoneInfo.Local 等。相反,使用 DateTime.UtcNow 获取当前的 DateTime。或者,您可以使用 DateTimeOffset.NowDateTimeOffset.UtcNow,或 Noda Time 的 IClock 实现中的方法之一。

归根结底,尽管您的问题有多种可能的解决方案,但在特定时区将当前时间生成为字符串的最简单方法是:

TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime utcNow = DateTime.UtcNow;
DateTime converted = TimeZoneInfo.ConvertTime(utcNow, destinationTimeZone);
string s = converted.ToString("yyyyMMddTHHmmss");

或者您可能需要:

TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTimeOffset utcNow = DateTimeOffset.UtcNow;
DateTimeOffset converted = TimeZoneInfo.ConvertTime(utcNow, destinationTimeZone);
string s = converted.ToString("yyyyMMddTHHmmsszzz").Replace(":","");

注意最后通过 Replace 删除了 : - 这是因为在 ISO 8601 basic 格式中,偏移量应该像 -0500 而不是 -05:00。不幸的是,没有格式说明符可以直接获取它。 (只有 ISO 8601 extended 格式使用冒号)。