Преобразование между часовыми поясами с помощью Noda Time

В настоящее время я пытаюсь обеспечить, чтобы наш устаревший back-end мог поддерживать разрешение на дату, основанное на текущем часовом поясе пользователя (или, более точно, на смещение). Наши серверы находятся в восточном стандартном времени, и большинство наших дат встречаются там. Тем не менее, для пользователей, находящихся в других часовых поясах, при получении этих дат времени требуется преобразование в их часовой пояс (или, в данном случае, смещение). Кроме того, время, приходящее от пользователя, должно быть преобразовано в восточное стандартное время до сохранения на сервере. Учитывая, что передний интерфейс, который мы разрабатываем, основан на веб-интерфейсе, я могу получить смещение пользователя за считанные минуты и передать это значение в мой сервисный уровень внутри заголовка. Я посмотрел на Noda Time и подумал, что это отличный API. Это заставило меня задуматься о времени в более изысканном вопросе, но я все еще не уверен на 100%, что правильно правильно использовал его. Вот методы, которые я написал для описанных выше преобразований. Я тестировал их, и они, похоже, работают. Учитывая описанный выше сценарий, похоже ли это на правильное использование библиотеки? Думаю ли я правильно о датах?

public static DateTime ConvertToUtcFromEasternTimeZone(DateTime easternDateTime)
{
    NodaTime.DateTimeZone easternTimeZone = NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York");
    ZoneLocalMappingResolver customResolver = Resolvers.CreateMappingResolver(Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);
    var easternLocalDateTime = LocalDateTime.FromDateTime(easternDateTime);
    var easternZonedDateTime = easternTimeZone.ResolveLocal(easternLocalDateTime, customResolver);
    return easternZonedDateTime.ToDateTimeUtc();
}

public static DateTime ConvertToEasternTimeZoneFromUtc(DateTime utcDateTime)
{
    NodaTime.DateTimeZone easternTimeZone = NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("America/New_York");
    NodaTime.DateTimeZone utcTimeZone = NodaTime.DateTimeZoneProviders.Tzdb.GetZoneOrNull("UTC");
    ZoneLocalMappingResolver customResolver = Resolvers.CreateMappingResolver(Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);
    var utcLocal = LocalDateTime.FromDateTime(utcDateTime);
    var utcZonedDateTime = utcTimeZone.ResolveLocal(utcLocal, customResolver);
    var easternZonedDateTime = utcZonedDateTime.ToInstant().InZone(easternTimeZone);
    return easternZonedDateTime.ToDateTimeUnspecified();
}

public static DateTime ConvertToUtc(DateTime dateTime, int offsetInMinutes)
{
    LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
    var convertedDateTime = localDateTime.PlusMinutes(offsetInMinutes).ToDateTimeUnspecified();
    return convertedDateTime;
}

public static DateTime ConvertFromUtc(DateTime dateTime, int offsetInMinutes)
{
    LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
    var convertedDateTime = localDateTime.PlusMinutes(-offsetInMinutes).ToDateTimeUnspecified();
    return convertedDateTime;
}

Идея здесь в том, что часовой пояс имеет значение, когда я решаю между временем UTC и часовым поясом в базе данных. Когда я решаю между временем клиента и временем UTC, тогда смещение вопросов.

В будущем мы сможем сохранить время UTC, и это будет проще. В настоящее время это решение является разрывной остановкой.

Идея состоит в том, что мы собираемся перейти от...

client → UTC +/- offset → UTC → Восточное время → база данных

база данных → Восточное время → UTC → UTC +/- offset → клиент

до конца...

client → UTC +/- offset → UTC → база данных

база данных → UTC → UTC +/- offset → клиент

Ответ 1

Ваш первый метод выглядит нормально, хотя мы не знаем, что такое customResolver.

Второй метод немного выключен. Я бы предложил:

public static DateTime ConvertToEasternTimeZoneFromUtc(DateTime utcDateTime)
{
    var easternTimeZone = DateTimeZoneProviders.Tzdb["America/New_York"];
    return Instant.FromDateTimeUtc(utcDateTime)
                  .InZone(easternTimeZone)
                  .ToDateTimeUnspecified();
}

Обратите внимание, что вам не нужно искать восточный часовой пояс в каждом вызове метода - просто:

private static readonly DateTimeZone EasternTimeZone = 
    DateTimeZoneProviders.Tzdb["America/New_York"];

... тогда используйте это везде.

Третий и четвертый методы - это не то, что я думаю о идиоматике - для третьего метода, который вы должны использовать:

public static DateTime ConvertToUtc(DateTime dateTime, int offsetInMinutes)
{
    var offset = Offset.FromMinutes(offsetInMinutes);
    var localDateTime = LocalDateTime.FromDateTime(dateTime);
    return new OffsetDateTime(localDateTime, offset).ToInstant()
                                                    .ToDateTimeUtc();
}

Четвертый метод кажется немного сложнее, поскольку мы не предоставляем все, что нужно, с точки зрения конверсий с OffsetDateTime. Код, который вы использовали, вероятно, хорошо, но он, безусловно, был бы более чистым, если бы вы могли использовать OffsetDateTime.

EDIT: теперь я добавил метод Instant, чтобы сделать четвертый метод более чистым. Он будет частью 1.2.0, и вы можете использовать:

public static DateTime ConvertFromUtc(DateTime dateTime, int offsetInMinutes)
{
    var offset = Offset.FromMinutes(offsetInMinutes);
    var instant = Instant.FromDateTimeUtc(dateTime);
    return instant.WithOffset(offset)
                  .LocalDateTime
                  .ToDateTimeUnspecified();
}

Ответ 2

Я хотел бы добавить, что первый метод может быть переписан без customResolver.

using System;
using NodaTime;

namespace qwerty
{
    class Program
    {
        static void Main(string[] args)
        {
            var convertedInUTC = ConvertToUtcFromCustomTimeZone("America/Chihuahua", DateTime.Now);
            Console.WriteLine(convertedInUTC);
        }

        private static DateTime ConvertToUtcFromCustomTimeZone(string timezone, DateTime datetime) 
        {
            DateTimeZone zone = DateTimeZoneProviders.Tzdb[timezone];
            var localtime = LocalDateTime.FromDateTime(datetime);
            var zonedtime = localtime.InZoneLeniently(zone);
            return zonedtime.ToInstant().InZone(zone).ToDateTimeUtc();
        }
    }
}