首页 > 解决方案 > 将 Nodatime 中的时间舍入到最接近的时间间隔

问题描述

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

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

如果不使用 Nodatime,您可以执行以下操作:

// 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

解决方案


对于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构建一个新版本。我们没有为此提供方便的方法,但这只是调用构造函数的问题。OffsetDateTimeLocalDateTime

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

你可以做的是:

  • 称呼ZonedDateTime.ToOffsetDateTime
  • OffsetDateTime如上圆
  • 打电话OffsetDateTime.InZone(zone)回一个ZonedDateTime

然后,您可以检查结果的偏移量ZonedDateTime是否与原始偏移量相同,如果您想检测奇怪的情况 - 但您需要决定如何处理它们。不过,这种行为是相当合理的——如果你从ZonedDateTime(比如说)01:47 的时间部分开始,你会ZonedDateTime在 7 分钟前的同一时区结束。如果在最后 7 分钟内发生转换,则可能不会是 01:40……但我怀疑您实际上不需要担心。


推荐阅读