【问题标题】:Rounding time in Nodatime to nearest interval将 Nodatime 中的时间舍入到最接近的时间间隔
【发布时间】:2020-04-17 08:42:17
【问题描述】:

我们需要将时间设置为最近的任意间隔(例如,由时间跨度或持续时间表示)。

假设我们需要将它降到最接近的十分钟。 例如13:02 变为 13:00,14:12 变为 14:10

如果不使用 Nodatime,您可以执行 this: 之类的操作

// Floor
long ticks = date.Ticks / span.Ticks;
return new DateTime( ticks * span.Ticks );

这将使用时间跨度的刻度将日期时间设置为特定时间。

似乎 NodaTime 暴露了一些我们以前没有考虑过的复杂性。你可以这样写一个函数:

public static Instant FloorBy(this Instant time, Duration duration)
=> time.Minus(Duration.FromTicks(time.ToUnixTimeTicks() % duration.BclCompatibleTicks));

但该实现似乎并不正确。 “最近十分钟的地板”似乎取决于时区/时间偏移量。 虽然在 UTC 中可能是 13:02,但在偏移量为 +05:45 的尼泊尔,时间将是 18:47。

这意味着在 UTC 中,精确到十分钟意味着减去两分钟,而在尼泊尔,这意味着减去七分钟。

我觉得我应该能够以某种方式将 ZonedDateTime 或 OffsetDateTime 舍入任意时间跨度。我可以通过编写这样的函数来接近

public static OffsetDateTime FloorToNearestTenMinutes(this OffsetDateTime time)
{
    return time
        .Minus(Duration.FromMinutes(time.Minute % 10))
        .Minus(Duration.FromSeconds(time.Second));
}

但这不允许我指定任意持续时间,因为 OffsetDateTime 没有刻度的概念。

如何在考虑时区的情况下以任意间隔正确舍入 Instant/ZonedDateTime/OffsetDateTime?

【问题讨论】:

    标签: c# nodatime


    【解决方案1】:

    对于OffsetDateTime,我建议你写一个Func<LocalTime, LocalTime>,它实际上是野田时间术语中的“调整者”。然后你就可以使用With 方法:

    // This could be a static field somewhere - or a method, so you can use
    // a method group conversion.
    Func<LocalTime, LocalTime> adjuster =>
        new LocalTime(time.Hour, time.Minute - time.Minute % 10, 0);
    
    // The With method applies the adjuster to just the time portion,
    // keeping the date and offset the same.
    OffsetDateTime rounded = originalOffsetDateTime.With(adjuster);
    

    请注意,这只有效,因为您的四舍五入永远不会改变日期。如果您需要一个也可以更改日期的版本(例如,将 23:58 舍入到第二天的 00:00),那么您需要获取新的LocalDateTime 并使用该LocalDateTime 构造一个新的OffsetDateTime和原始偏移量。我们没有为此提供方便的方法,但只需调用构造函数即可。

    由于您给出的原因,

    ZonedDateTime从根本上更棘手。目前,尼泊尔不遵守夏令时 - 但它可能会在未来这样做。在 DST 边界附近四舍五入可能会使您进入模棱两可甚至跳过的时间。这就是为什么我们不为ZonedDateTime 提供类似的With 方法。 (在您的情况下,这不太可能,尽管从历史上看是可能的……使用日期调整器,您很容易陷入这种情况。)

    你可以做的是:

    • 致电ZonedDateTime.ToOffsetDateTime
    • 如上圆 OffsetDateTime
    • 致电OffsetDateTime.InZone(zone) 回复ZonedDateTime

    可以然后检查生成的 ZonedDateTime 的偏移量是否与原始偏移量相同,如果您想检测奇怪的情况 - 但您需要决定实际做什么关于他们。不过,这种行为是相当合理的 - 如果您以时间部分为(例如)01:47 的 ZonedDateTime 开始,那么您将在 7 分钟前的同一时区中以 ZonedDateTime 结束。 可能不会是 01:40,如果过渡发生在最后 7 分钟内...但我怀疑你实际上不需要担心它.

    【讨论】:

      【解决方案2】:

      我最终从 Jon Skeets 的回答中获取了一些东西,并滚动了我自己的 Rounder,它需要一个任意的 Duration 来进行舍入。 (这是我需要的关键之一,这也是我不接受这个答案的原因)。

      根据 Jons 的建议,我将 Instant 转换为 OffsetDateTime 并应用取整器,这需要任意持续时间。示例和实现如下:

      // Example of usage
      public void Example()
      {
          Instant instant = SystemClock.Instance.GetCurrentInstant();
          OffsetDateTime offsetDateTime = instant.WithOffset(Offset.Zero);
          var transformedOffsetDateTime = offsetDateTime.With(t => RoundToDuration(t, Duration.FromMinutes(15)));
          var transformedInstant = transformedOffsetDateTime.ToInstant();
      }
      
      // Rounding function, note that it at most truncates to midnight at the day.
      public static LocalTime RoundToDuration(LocalTime timeToTransform, Duration durationToRoundBy)
      {
          var ticksInDuration = durationToRoundBy.BclCompatibleTicks;
          var ticksInDay = timeToTransform.TickOfDay;
          var ticksAfterRounding = ticksInDay % ticksInDuration;
          var period = Period.FromTicks(ticksAfterRounding);
      
          var transformedTime = timeToTransform.Minus(period);
          return transformedTime;
      }
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-09
        • 1970-01-01
        • 2010-10-13
        • 1970-01-01
        • 2018-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多