时区转换问题

时间:2014-02-22 22:09:26

标签: c# timezone

我正在开发一个使用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中是否存在边界条件错误。 谢谢你看看。

3 个答案:

答案 0 :(得分:2)

你问为什么tzInfoM.IsAmbiguousTime(convertedTimeM)返回true?

夏令时于2014年2月11日结束,因此山区时区的DateTime值为11/2/2014 1:00 AM不明确;它可以解释为Mountain Daylight Time或Mountain Standard Time。

实际上,如果您使用convertedTimeMtzInfoM转换回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不是。