使用NodaTime将无效(跳过)日期时间值转换为UTC

时间:2013-03-04 23:34:00

标签: c# datetime timezone dst nodatime

我想要完成的是将DateTime(从假设在EST / EDT中的字符串解析)转换为UTC。我正在使用NodaTime,因为我需要使用Olson时区。

使用NodaTime的ZoneLocalMappingResolver将无效(跳过)DateTime转换为UTC不会转换输入的分钟和秒部分,因为我已将CustomResolver配置为返回间隙后的间隔开始。 NodaTime似乎没有TimeZoneInfo.IsInvalidTime的等价物。

如何使用NodaTime将跳过的日期时间值转换为UTC并匹配下面的Utils类中的GetUtc()方法的结果? (Utils.GetUtc方法使用System.TimeZoneInfo而不是NodaTime)

这是测试用例:

[TestMethod]
public void Test_Invalid_Date()
{
    var ts = new DateTime(2013, 3, 10, 2, 15, 45);

    // Convert to UTC using System.TimeZoneInfo
    var utc = Utils.GetUtc(ts).ToString(Utils.Format);

    // Convert to UTC using NodaTime (Tzdb/Olson dataabase)
    var utcNodaTime = Utils.GetUtcTz(ts).ToString(Utils.Format);

    Assert.AreEqual(utc, utcNodaTime);
}

这就是我得到的:

Assert.AreEqual失败。预期:< 2013-03-10 07:15:45.000000>。实际:< 2013-03-10 07:00:00.000000>。

这是Utils类(也在github上):

using System;

using NodaTime;
using NodaTime.TimeZones;

/// <summary>
/// Functions to Convert To and From UTC
/// </summary>
public class Utils
{
    /// <summary>
    /// The date format for display/compare
    /// </summary>
    public const string Format = "yyyy-MM-dd HH:mm:ss.ffffff";

    /// <summary>
    /// The eastern U.S. time zone
    /// </summary>
    private static readonly NodaTime.DateTimeZone BclEast = NodaTime.DateTimeZoneProviders.Bcl.GetZoneOrNull("Eastern Standard Time");


    private static readonly TimeZoneInfo EasternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

    private static readonly NodaTime.DateTimeZone TzEast = NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York");

    private static readonly ZoneLocalMappingResolver CustomResolver = Resolvers.CreateMappingResolver(Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);

    public static DateTime GetUtc(DateTime ts)
    {
        return TimeZoneInfo.ConvertTimeToUtc(EasternTimeZone.IsInvalidTime(ts) ? ts.AddHours(1.0) : ts, EasternTimeZone);
    }

    public static DateTime GetUtcTz(DateTime ts)
    {
        var local = LocalDateTime.FromDateTime(ts);
        var zdt = TzEast.ResolveLocal(local, CustomResolver);
        return zdt.ToDateTimeUtc();            
    }

    public static DateTime GetUtcBcl(DateTime ts)
    {
        var local = LocalDateTime.FromDateTime(ts);
        var zdt = BclEast.ResolveLocal(local, CustomResolver);
        return zdt.ToDateTimeUtc();
    }
}

1 个答案:

答案 0 :(得分:7)

  

NodaTime似乎没有等效的TimeZoneInfo.IsInvalidTime。

好吧,不仅仅是问一个问题 - 然后不得不问下一个问题 - 你使用DateTimeZone.MapLocal。这为您提供了有关本地到UTC映射的所有信息:它是否明确,模糊或无效。

或者,使用ResolveLocal,但使用您自己的自定义SkippedTimeResolver委托。

例如,进行此更改会使您的代码对我有用:

private static readonly ZoneLocalMappingResolver CustomResolver = 
    Resolvers.CreateMappingResolver(Resolvers.ReturnLater, AddGap);

// SkippedTimeResolver which adds the length of the gap to the
// local date and time.
private static ZonedDateTime AddGap(LocalDateTime localDateTime,
                                    DateTimeZone zone,
                                    ZoneInterval intervalBefore,
                                    ZoneInterval intervalAfter)        
{
    long afterMillis = intervalAfter.WallOffset.Milliseconds;
    long beforeMillis = intervalBefore.WallOffset.Milliseconds;
    Period gap = Period.FromMilliseconds(afterMillis - beforeMillis);
    return zone.AtStrictly(localDateTime + gap);
}

(当然还有其他等效方法。)

我个人建议尽量避免转换到DateTime以及从{{1}}转换,除非你真的需要 - 我会尽可能多地在Noda Time中做。