使用Noda Time在不同时区之间转换时间

26

我目前正在努力确保我们的遗留后端能够支持根据用户的当前时区(或更具体地说是偏移量)解析日期时间。我们的服务器在东部标准时间,大多数日期时间原始来自那里。但是对于处于其他时区的用户,需要将这些日期时间转换为他们的时区(或在此情况下为偏移量)进行检索。此外,在持久化到服务器之前,用户提交的日期时间也必须转换为东部标准时间。鉴于我们正在开发的前端基于Web,我可以获取用户的偏移值(单位:分钟),并在头部将该值传递到我的服务层中。我看了看Noda Time,觉得它是一个很好的API。它让我更加细致地思考时间问题,但我仍然不确定我是否正确使用了它。这里是我为上述转换编写的方法。我已经测试过它们,它们似乎有效。在上述情况下,这是否看起来是库的正确使用方式?我是否正确地思考了日期和时间?

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;
}

这里的想法是,当我在协调世界时和数据库中的时区之间比对时,时区是很重要的。而当我在协调世界时和客户端时间之间比对时,偏移量是很重要的。

未来我们可以持久化协调世界时,那会更加容易。目前,这个解决方案是权宜之计。

我们的想法是从...

客户端 -> 协调世界时 +/- 偏移量 -> 协调世界时 -> 东部时间 -> 数据库

数据库 -> 东部时间 -> 协调世界时 -> 协调世界时 +/- 偏移量 -> 客户端

最终过渡到...

客户端 -> 协调世界时 +/- 偏移量 -> 协调世界时 -> 数据库

数据库 -> 协调世界时 -> 协调世界时 +/- 偏移量 -> 客户端


1
当你说“我们的服务器在东部标准时间”时,你实际上是指“我们的服务器在东部时间”(一年中在EST和EDT之间变化)吗?这很重要。 - Jon Skeet
是的,Jon。谢谢你的澄清。我确实需要考虑夏令时。 - PureCognition
2个回答

54

你的第一种方法看起来还不错,尽管我们不知道 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 的话肯定更加简洁。
编辑:我现在已经添加了一个方法到 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();
}

非常感谢,Jon。老实说,我对解析器的工作原理有点不清楚。根据我所读的内容,它们似乎可以用来考虑夏令时。然而,我注意到您在ConvertToEasternTimeZoneFromUtc()的实现中没有使用它。我有兴趣在数据库持久化日期时间和UTC之间进行转换时考虑夏令时。对于在UTC和客户端日期时间之间进行转换,我不必担心它,因为我从客户端得到了偏移量(它隐含地考虑了DST)。 - PureCognition
1
另外,我所使用的库版本没有Offset.FromMinutes()方法。因此,我将其转换为Offset.FromHoursAndMinutes(0, offsetInMinutes)。 - PureCognition

0

我想补充说,第一种方法可以在没有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();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接