我们正在尝试构建基本的事件日历功能,该功能允许用户创建事件并指定给定月、日、年、小时和分钟的开始时间以及时区(system.timezoneinfo.id
)。CMS系统根据服务器的位置生成结果system.DateTime
,比方说TimeZoneInfo.id
山区标准时间。CMS没有在其日期选择器组件中提供指定时区的选项。但是,我们可以控制SQL datetime精度,默认设置为7
。
datetime
被格式化为yyyymmddthhmmssz
,以便在.ics/ical中填充开始/结束时间。使用这种格式,它会使2018年5月25日7:00PM(20180508T192840Z
)始终看起来像服务器的山区标准时间(MST),而不是选择的东部标准时间(EST)中的2018年5月25日7:00PM。
如何将在不更改年/月/日/小时/分钟的情况下生成的datetime
、datetimeoffset
、timezoneinfo
、nodatime
或甚至string
函数“替换”datetime
的时区,以将其格式化为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
来完成这一任务?
几件事:
>
您的格式说明符在末尾包含z
。这被.NET的字符串格式视为字符文本,因为它不是有效的日期时间格式说明符。请注意,格式标记区分大小写。作为文字,它只是复制到输出--就像t
一样。因此,您生成的字符串总是会被解析它的任何东西解释为UTC,因为这是ISO8601标准中z
的意思。这是你所面临问题的根本原因。
如果您希望它反映一个不明确的本地时间(因为时区可能在.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
,则它将被视为本地计算机的时区-在您的情况下是服务器的时区。
请注意,无论服务器的时区设置为什么,以行为相同的方式编写代码要好得多。这通常意味着避免DateTimeKind.Local
,如DateTime.Now
、TimeZoneInfo.Local
等。相反,使用datetime.utcNow
获取当前的datetime
。或者,您可以使用datetimeoffset.now
或datetimeoffset.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基本格式中,偏移量应该类似于-0500
而不是-05:00
。不幸的是,没有格式说明符来直接获取它。(只有ISO 8601扩展格式使用冒号)。