我正在开发一个使用UTC日期的程序,并且需要在某些情况下转换为本地时间。我写了一个更大的程序来测试边界条件,当我将“11/2/2014 02:00 EST”转换为山区时区时似乎有问题。
我将所有内容从测试程序中删除,以创建一个示例,其中包含用于演示问题的最低限度。这是VS2010 .Net 3.5控制台应用程序。
using System.Text;
namespace DSTDebug
{
class Program
{
static void Main(string[] args)
{
// get the system EST and MST time zone info structures
TimeZoneInfo tzEastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
TimeZoneInfo tzMountain = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
// create a time, 11/2/2014 02:00 EST
// using DateTimeOffset, it should not be an ambiguous time
// converting to Mountain Standard Time should not be ambiguous either but it's showing up that way
// 11/2/2014 02:00 EST should be 11/2/2014 01:00 MDT
DateTimeOffset dateToTest = new DateTimeOffset(2014, 11, 2, 2, 0, 0, TimeSpan.FromHours(tzEastern.BaseUtcOffset.Hours));
DateTime sampleTimeUTC = dateToTest.UtcDateTime;
ShowConversion(sampleTimeUTC, tzMountain, tzEastern);
Console.WriteLine(string.Empty);
}
private static void ShowConversion(DateTime sampleTimeUTC, TimeZoneInfo tzInfoM, TimeZoneInfo tzInfoE)
{
DateTime sampleTime = TimeZoneInfo.ConvertTimeFromUtc(sampleTimeUTC, tzInfoE);
DateTime convertedTimeM = TimeZoneInfo.ConvertTimeFromUtc(sampleTimeUTC, tzInfoM);
// write the output: {0} is sampleTime; {1} is "*" if sampleTime is DST; {2} is convertedTimeM; {3} is "*" if convertedTimeM is DST
Console.WriteLine(" {0:MM/dd/yy HH:mm}{1} => {2:MM/dd/yy HH:mm}{3}", sampleTime, tzInfoE.IsDaylightSavingTime(sampleTime) ? "*" : " ",
convertedTimeM, tzInfoM.IsDaylightSavingTime(convertedTimeM) ? "*" : " ");
// information: display if the sample time or converted time is ambiguous
Console.WriteLine(" Sample time is{0} ambiguous", tzInfoE.IsAmbiguousTime(sampleTimeUTC) ? string.Empty : " not");
Console.WriteLine(" Converted time is{0} ambiguous", tzInfoM.IsAmbiguousTime(convertedTimeM) ? string.Empty : " not");
}
}
}
我想知道我是否有编码错误,或者.Net中是否存在边界条件错误。 谢谢你看看。
答案 0 :(得分:2)
你问为什么tzInfoM.IsAmbiguousTime(convertedTimeM)
返回true?
夏令时于2014年2月11日结束,因此山区时区的DateTime
值为11/2/2014 1:00 AM不明确;它可以解释为Mountain Daylight Time或Mountain Standard Time。
实际上,如果您使用convertedTimeM
将tzInfoM
转换回UTC,则会得到与您开始时不同的时间,因为ConvertTimeToUtc
会假定标准时间内的模糊时间:
// sampleTimeUTC = 11/2/2014 7:00 AM UTC
DateTime convertedTimeM = TimeZoneInfo.ConvertTimeFromUtc(sampleTimeUTC, tzInfoM);
// convertedTimeM = 11/2/2014 1:00 AM
DateTime convertedTimeUTC = TimeZoneInfo.ConvertTimeToUtc(convertedTimeM, tzInfoM);
// convertedTimeUTC = 11/2/2014 8:00 AM UTC
所以.NET正如预期的那样工作。 。 。但这并不一定意味着您的代码也有错误。你想做什么?
更新:使用NodaTime是一个不错的选择。为了完整起见,这里是如何使用BCL类型:
static void Main(string[] args)
{
TimeZoneInfo tzEastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
TimeZoneInfo tzMountain = TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time");
// 11/2/2014 02:00 EST should be 11/2/2014 01:00 MDT
DateTimeOffset dateToTest;
dateToTest = new DateTimeOffset(2014, 11, 2, 2, 0, 0, TimeSpan.FromHours(-5));
ShowConversion(dateToTest, tzEastern, tzMountain);
// 11/2/2014 03:00 EST should be 11/2/2014 01:00 MST
dateToTest = new DateTimeOffset(2014, 11, 2, 3, 0, 0, TimeSpan.FromHours(-5));
ShowConversion(dateToTest, tzEastern, tzMountain);
}
private static void ShowConversion(DateTimeOffset dateToTest, TimeZoneInfo tzEastern, TimeZoneInfo tzMountain)
{
DateTimeOffset convertedTime = TimeZoneInfo.ConvertTime(dateToTest, tzMountain);
Console.WriteLine("{0:MM/dd/yyyy HH:mm} {1} => {2:MM/dd/yyyy HH:mm} {3}",
dateToTest, tzEastern.IsDaylightSavingTime(dateToTest) ? "EDT" : "EST",
convertedTime, tzMountain.IsDaylightSavingTime(convertedTime) ? "MDT" : "MST");
}
答案 1 :(得分:1)
我放弃了使用Microsoft对象。在我的研究中,我读到了Nodatime,并决定尝试一下。我想出的第一枪就是正确的。 可能有很多地方可以更清洁,但也许这可以帮助某人。
using System;
using NodaTime;
using NodaTime.TimeZones;
namespace NodaDSTDebug
{
class Program
{
static void Main(string[] args)
{
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Bcl;
// get the system EST and MST time zone info structures
DateTimeZone tzEastern = timeZoneProvider["Eastern Standard Time"];
DateTimeZone tzMountain = timeZoneProvider["Mountain Standard Time"];
// 11/2/2014 02:00 EST should be 11/2/2014 01:00 MDT
ZonedDateTime dateToTest;
dateToTest = new ZonedDateTime(new LocalDateTime(2014, 11, 2, 2, 0, 0), tzEastern, Offset.FromHours(-5));
ShowConversion(dateToTest, tzMountain);
// 11/2/2014 03:00 EST should be 11/2/2014 01:00 MST
dateToTest = new ZonedDateTime(new LocalDateTime(2014, 11, 2, 3, 0, 0), tzEastern, Offset.FromHours(-5));
ShowConversion(dateToTest, tzMountain);
}
/// <summary>
/// Convert Eastern Time into Mountain Time
/// </summary>
/// <param name="dateToTest">Eastern Time</param>
/// <param name="tzMountain">Mountain TimeZone</param>
private static void ShowConversion(ZonedDateTime dateToTest, DateTimeZone tzMountain)
{
ZonedDateTime convertedTime = dateToTest.WithZone(tzMountain);
// for some reason the 'x' format pattern is not working, so they are ginned up as constants
// 'x' is printing the full timezone name e.g. "Eastern Standard Time"
Console.WriteLine("{0:MM/dd/yyyy HH:mm x} {1} => {2:MM/dd/yyyy HH:mm x} {3}",
dateToTest, IsDaylightSavingsTime(dateToTest) ? "EDT" : "EST",
convertedTime, IsDaylightSavingsTime(convertedTime) ? "MDT" : "MST");
}
// From http://stackoverflow.com/questions/15211052/what-is-the-system-timezoneinfo-isdaylightsavingtime-equivalent-in-nodatime
// Thanks to Matt Johnson
public static bool IsDaylightSavingsTime(ZonedDateTime zonedDateTime)
{
Instant instant = zonedDateTime.ToInstant();
ZoneInterval zoneInterval = zonedDateTime.Zone.GetZoneInterval(instant);
return zoneInterval.Savings != Offset.Zero;
}
}
}
节目输出:
11/02/2014 02:00 EST => 11/02/2014 01:00 MDT
11/02/2014 03:00 EST => 11/02/2014 01:00 MST
Press any key to continue . . .
答案 2 :(得分:0)
您的代码示例中有很多不必要的仪式,但我看到的唯一潜在错误是使用TimeSpan.FromHours(tzEastern.BaseUtcOffset.Hours)
获取偏移量的位置。这可以写得更短,只有tzEastern.BaseUtcOffset
,但是,这只会起作用,因为基本偏移在您提供时生效。假设在您的生产代码中输入可能是可变的,那么您应该这样做:
DateTime dt = new DateTime(2014, 11, 2, 2, 0, 0);
DateTimeOffset dateToTest = new DateTimeOffset(dt, tzEastern.GetUtcOffset(dt));
请注意,如果dt
属于不明确的本地时间范围,GetUtcOffset
将采用标准时间。同样,如果dt
无效,则会返回标准偏移量。做这样的事情是合理的:
DateTime dt = // coming from your input
if (tzEastern.IsInvalidTime(dt))
{
// Throw an exception to tell your user that the input is invalid.
}
else if (tzEastern.IsAmbiguousTime(dt))
{
// Throw an exception to ask your user to pick from either EST (-5) or EDT (-4).
// Use their choice of offset as a new input parameter.
}
DateTimeOffset dateToTest = new DateTimeOffset(dt, tzEastern.GetUtcOffset(dt));
我建议如果用户徘徊,你确实提示他们。但是,如果您的应用无法实现这一目标,那么您应该简单地决定要遵循的规则。
就个人而言,我认为支持白天时间而不是标准时间更有意义。 (想想:如果时间无效,那么他们就不会考虑DST,所以我会为他们增加时间;如果时间不明确,那么有两种可能性,选择第一种可能是有意义的。)但是这只是我的意见。 YMMV。
关于您当前代码显示convertedTimeM
含糊不清的原因 - 在您的代码中,您只有DateTime
DateTimeKind.Unspecified
。由于有两个1点,那确实是暧昧的。迈克尔刘在钉子上打了它 - 如果你把它保持为DateTimeOffset
那么它永远不会模棱两可,你不必检查。如果你有一个DateTimeOffset
,你可以将输出保持在DateTimeOffset
。此外,这消除了在中间转换为UTC DateTime
的需要。
另外你应该注意到亚利桑那州的Id是“美国山地标准时间” - 这是MST总是,而“山地标准时间”是指在MST和MDT之间交替的其余山区时区。
最后,您可能希望查看the DST tag wiki中的图表。这应该清楚地说明UTC到本地是确定性的,而本地到UTC不是。