Использование NodaTime для преобразования недопустимых (пропущенных) значений даты и времени в UTC

Чего я хочу добиться, так это преобразовать DateTime (проанализированный из строки, которая, как предполагается, находится в EST/EDT) в UTC. Я использую NodaTime, потому что мне нужно использовать часовые пояса Олсона.

Преобразование недействительного (пропущенного) DateTime в UTC с использованием NodaTime ZoneLocalMappingResolver не преобразует часть ввода в минутах и ​​секундах, потому что я настроил CustomResolver для возврата начала интервала после промежутка. NodaTime, похоже, не имеет эквивалента TimeZoneInfo.IsInvalidTime.

Как использовать NodaTime для преобразования пропущенных значений даты и времени в UTC и сопоставления результата метода GetUtc() в классе Utils ниже? (Метод 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();
    }
}

person ash    schedule 04.03.2013    source источник


Ответы (1)


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 и из него, если вам это действительно не нужно - я бы сделал все возможное в Noda Time.

person Jon Skeet    schedule 05.03.2013
comment
Я обновил справочные ссылки в этом ответе, чтобы они вели к вашей новой документации по API, размещенной на nodatime.org, а не на старом отсутствующем сайте кода Google. - person clarkitect; 08.03.2016