【问题标题】:Noda Time - Create a ZonedDateTime from DateTime and TimeZoneIdNoda Time - 从 DateTime 和 TimeZoneId 创建 ZonedDateTime
【发布时间】:2016-10-21 19:59:39
【问题描述】:

假设我有以下日期、时间和时区:2016-10-15, 1:00:00, America/Toronto

如何创建一个ZonedDateTime 来表示指定区域中的确切日期和时间?

基本上我需要一个ZonedDateTime 对象,它代表确切时区中的确切日期和时间。

如果时间被跳过,我想将小时刻度添加到新时间。示例:

如果 00:00 被跳过到 1:00,并且我尝试获取区域中的时间 00:30,我希望结果是 1:30,而不仅仅是 1:00,这是第一次间隔。

如果将 00:00 跳到 1:45,并且我尝试获取该区域中的时间 00:20,我希望结果不是 2:05。

如果时间不明确,i。 e.,出现两次,我要早一点的映射。

【问题讨论】:

  • 您可能对此感兴趣.. stackoverflow.com/questions/21945436/…
  • @Veeram 非常感谢您!正是我需要的。
  • 如果该日期/时间不存在或出现两次,您希望发生什么?我可以从您答案中的代码中推断出它,但要求应该在您的 question 中。
  • @JonSkeet 并不是我没有提到它们,而是我根本没有真正意识到这个问题的存在,因为我没有考虑到 tz 的变化。只有在我了解了 3 种 InZone 方法之间的区别之后,才发现某个特定时间可能不存在于 tz 中,因为它被跳过了,或者可能出现两次。
  • 对,但是一旦你意识到问题的存在,你应该把你更新的需求放在问题中。目前你说你想要一个代表时区确切日期和时间的ZonedDateTime - 但你的 answer 没有给出。 (如果“期望的”时间是凌晨 1:30 但被跳过了,那么您将给出凌晨 2:30。)

标签: c# datetime nodatime


【解决方案1】:

您所描述的正是 Noda Time 2.0 中 LocalDateTime.InZoneLeniently 的行为。 (感谢Matt Johnson's change :) 但是,由于它仍处于 alpha 阶段,因此这是 1.3.2 的解决方案。基本上,您只需要一个合适的ZoneLocalMappingResolver,您可以使用Resolvers 构建它。这是一个完整的例子。

using NodaTime.TimeZones;
using NodaTime.Text;

class Program
{
    static void Main(string[] args)
    {
        // Paris went forward from UTC+1 to UTC+2
        // at 2am local time on March 29th 2015, and back
        // from UTC+2 to UTC+1 at 3am local time on October 25th 2015.
        var zone = DateTimeZoneProviders.Tzdb["Europe/Paris"];

        ResolveLocal(new LocalDateTime(2015, 3, 29, 2, 30, 0), zone);
        ResolveLocal(new LocalDateTime(2015, 6, 19, 2, 30, 0), zone);
        ResolveLocal(new LocalDateTime(2015, 10, 25, 2, 30, 0), zone);
    }

    static void ResolveLocal(LocalDateTime input, DateTimeZone zone)
    {
        // This can be cached in a static field; it's thread-safe.
        var resolver = Resolvers.CreateMappingResolver(
            Resolvers.ReturnEarlier, ShiftForward);

        var result = input.InZone(zone, resolver);
        Console.WriteLine("{0} => {1}", input, result);
    }

    static ZonedDateTime ShiftForward(
        LocalDateTime local,
        DateTimeZone zone,
        ZoneInterval intervalBefore,
        ZoneInterval intervalAfter)
    {
        var instant = new OffsetDateTime(local, intervalBefore.WallOffset)
            .WithOffset(intervalAfter.WallOffset)
            .ToInstant();
        return new ZonedDateTime(instant, zone);
    }            
}

输出:

29/03/2015 02:30:00 => 2015-03-29T03:30:00 Europe/Paris (+02)
19/06/2015 02:30:00 => 2015-06-19T02:30:00 Europe/Paris (+02)
25/10/2015 02:30:00 => 2015-10-25T02:30:00 Europe/Paris (+02)

【讨论】:

  • 确实好多了,谢谢!只有一个问题:是否有可能知道使用此解决方案是否跳过了时间?我正在使用 SkippedTimeException 设置标志。现在看来我必须做类似 (input + result offset) != result -> skipped 之类的事情,对吧?
  • @victor:你可以使用if (result.LocalDateTime != input) - 基本上就是说“如果我必须调整任何东西,它一定被跳过了......”
  • 可以肯定地说,intervalAfter 将始终是“本地”的当前偏移量吗?
  • @victor:嗯,intervalAfterZoneInterval,而不是 Offset - 但是是的,如果它被转移了,那么根据瞬间的构造方式,它应该有那个偏移量。
【解决方案2】:

编辑
之前的解决方案存在问题,例如夏令时期间的日期时间无效等。

这是解决所有问题的新解决方案,并附有解释。 感谢@Veeram。

// Transform the "time" in a localized time.                
var tzLocalTime = LocalDateTime.FromDateTime(time);

try
{
    // To get the exact same time in the specified zone.
    zoned = tzLocalTime.InZoneStrictly(zone);
}
catch(SkippedTimeException)
{
    // This happens if the time is skipped
    // because of daylight saving time.
    //
    // Example:
    // If DST starts at Oct 16 00:00:00,
    // then the clock is advanced by 1 hour
    // which means Oct 16 00:00:00 is *skipped*
    // to Oct 16 01:00:00.
    // In this case, it is not possible to convert
    // to exact same date, and SkippedTImeException
    // is thrown.

    // InZoneLeniently will convert the time
    // to the start of the zone interval after
    // the skipped date.
    // For the example above, this would return Oct 16 01:00:00.

     // If someone schedules an appointment at a time that
     // will not occur, than it is ok to adjust it to what
     // will really happen in the real world.

     var originalTime = ste.LocalDateTime;

     // Correct for the minutes, seconds, and milliseconds.
     // This is needed because if someone schedueld an appointment
     // as 00:30:00 when 00:00:00 is skipped, we expect the minute information
     // to be as expected: 01:30:00, instead of 01:00:00.
     var minuteSecondMillisecond = Duration.FromMinutes(originalTime.Minute) + Duration.FromSeconds(originalTime.Second) + Duration.FromMilliseconds(originalTime.Millisecond);

     zoned = zLocalTime.InZoneLeniently(zone).Plus(minuteSecondMillisecond);
}
catch(AmbiguousTimeException ate)
{
    // This happens when the time is ambiguous.
    // During daylight saving time, for example,
    // an hour might happen twice.
    //
    // Example:
    // If DST ends on Feb 19 00:00:00, then
    // Feb 18 23:00:00 will happen twice:
    // once during DST, and once when DST ends
    // and the clock is set back.
    // In such case, we assume the earlier mapping.
    // We could work with the second time that time
    // occur with ate.LaterMapping.

    zoned = ate.EarlierMapping;
}

【讨论】:

  • 当你不想要严格的语义时不要使用InZoneStrictly。您的第二段代码仍然损坏 - 不能保证它会以您想要的本地日期/时间结束。基本上,如果InZoneLenientlyInZoneStrictly 都不是你想要的,你应该调用InZone 并传入一个适当的ZoneLocalMappingResolver
  • 我知道它坏了,我在答案的开头说过。我只是没有删除代码来保留历史记录。问题是我想要严格的语义,如果可能的话。我希望能够捕获异常并明确处理它们。
  • 您可以看到我保留一天中的时间以防跳过小时,而 InZoneLeniently 会忽略它并返回间隔的第一个小时。你认为这是一个糟糕的方法吗?处理异常是我让它以这种方式工作的唯一方法
  • 这当然不是你让它工作的唯一方法——你可以使用ZoneLocalMappingResolver... 这就是重点。而且您并没有真正保持一天中的时间-您将一天中的时间作为持续时间添加到一天的开始,这根本不是一回事。 (基本上,您最终会提前跳过的时间量,因此凌晨 1:30 将变为凌晨 2:30。这是您想要做的吗?同样,您的要求应该在问题中......)
  • 另一种选择是使用DateTimeZone.MapLocal btw - 看看API documentation
猜你喜欢
  • 2010-11-22
  • 1970-01-01
  • 2014-02-13
  • 2017-05-04
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 2014-01-29
  • 2013-10-15
相关资源
最近更新 更多