Nodatime 中的四舍五入时间到最近的间隔
Rounding time in Nodatime to nearest interval
我们需要将时间下限到最接近的任意间隔(由例如时间跨度或持续时间表示)。
举个例子,我们需要将它下限到最近的十分钟。
例如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));
但该实现似乎不正确。
"Floor to nearest ten minutes"好像是依赖于timezone/offset的时候。
虽然在 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?
对于OffsetDateTime
,我建议你写一个Func<LocalTime, LocalTime>
,在Noda Time术语中实际上是一个"adjuster"。然后你可以只使用 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
并构建一个新的 OffsetDateTime
与 LocalDateTime
和原始偏移量。我们没有为此提供方便的方法,但这只是调用构造函数的问题。
由于您给出的原因,ZonedDateTime
从根本上说 更棘手。目前,尼泊尔不遵守夏令时 - 但将来可能会这样做。在 DST 边界附近四舍五入可能会使您进入模棱两可甚至跳过的时间。这就是为什么我们不为 ZonedDateTime
提供类似的 With
方法。 (在您的情况下,这不太可能,尽管从历史上看是可能的……使用日期调整器,您很容易会遇到这种情况。)
您可以做的是:
- 致电
ZonedDateTime.ToOffsetDateTime
- 如上
OffsetDateTime
四舍五入
- 调用
OffsetDateTime.InZone(zone)
返回 ZonedDateTime
你 可以 然后检查结果 ZonedDateTime
的偏移量是否与原始偏移量相同,如果你想检测奇怪的情况 - 但你然后需要决定如何实际处理它们。不过,这种行为是相当合理的——如果你从 ZonedDateTime
开始,时间部分是(比如)01:47,你最终会在同一时区从 7 开始得到 ZonedDateTime
几分钟前。 可能 如果在过去 7 分钟内发生转换,则不会 01:40...但我 怀疑 你不其实不用担心。
我最终从 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;
}
我们需要将时间下限到最接近的任意间隔(由例如时间跨度或持续时间表示)。
举个例子,我们需要将它下限到最近的十分钟。 例如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));
但该实现似乎不正确。 "Floor to nearest ten minutes"好像是依赖于timezone/offset的时候。 虽然在 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?
对于OffsetDateTime
,我建议你写一个Func<LocalTime, LocalTime>
,在Noda Time术语中实际上是一个"adjuster"。然后你可以只使用 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
并构建一个新的 OffsetDateTime
与 LocalDateTime
和原始偏移量。我们没有为此提供方便的方法,但这只是调用构造函数的问题。
ZonedDateTime
从根本上说 更棘手。目前,尼泊尔不遵守夏令时 - 但将来可能会这样做。在 DST 边界附近四舍五入可能会使您进入模棱两可甚至跳过的时间。这就是为什么我们不为 ZonedDateTime
提供类似的 With
方法。 (在您的情况下,这不太可能,尽管从历史上看是可能的……使用日期调整器,您很容易会遇到这种情况。)
您可以做的是:
- 致电
ZonedDateTime.ToOffsetDateTime
- 如上
OffsetDateTime
四舍五入 - 调用
OffsetDateTime.InZone(zone)
返回ZonedDateTime
你 可以 然后检查结果 ZonedDateTime
的偏移量是否与原始偏移量相同,如果你想检测奇怪的情况 - 但你然后需要决定如何实际处理它们。不过,这种行为是相当合理的——如果你从 ZonedDateTime
开始,时间部分是(比如)01:47,你最终会在同一时区从 7 开始得到 ZonedDateTime
几分钟前。 可能 如果在过去 7 分钟内发生转换,则不会 01:40...但我 怀疑 你不其实不用担心。
我最终从 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;
}