【问题标题】:Noda Time - Start/end of day with zone野田时间 - 带区域的一天的开始/结束
【发布时间】:2014-10-22 22:30:06
【问题描述】:

在运行代码的系统上设置的时区中,获取代表当天开始和结束的 ZonedDateTime(s) 的正确和更简洁的方法是什么?

下面的代码是不是太复杂了?

ZonedDateTime nowInZone = SystemClock.Instance.Now.InZone(DateTimeZoneProviders.Bcl.GetSystemDefault());

ZonedDateTime start = new LocalDateTime(nowInZone.Year, nowInZone.Month, nowInZone.Day, 0, 0, 0).InZoneStrictly(DateTimeZoneProviders.Bcl.GetSystemDefault());

ZonedDateTime end = new LocalDateTime(nowInZone.Year, nowInZone.Month, nowInZone.Day, 23, 59, 59).InZoneStrictly(DateTimeZoneProviders.Bcl.GetSystemDefault());

鉴于这些值,我需要测试它们之间是否有另一个 ZonedDateTime。

【问题讨论】:

  • 您可能想改用InZoneLeniently。例如,巴西在 DST 向前过渡时,从晚上 11:59 到凌晨 1 点。没有中午 12:00。 InZoneStrictly 会在这种情况下抛出。 InZoneLeniently 在转换 12am 的 LocalDateTime 时会转到 1am(如果我正确阅读了文档)。同样,在向后过渡时,它从晚上 11 点 59 分到晚上 11 点。晚上 11 点 59 分有两个。 InZoneLeniently 选择后一个。
  • @mikez - 关于巴西的好点子。我演示的方法已经涵盖了这一点。谢谢。

标签: c# .net timezone nodatime


【解决方案1】:

DateTimeZone 对象上的 AtStartOfDay 值具有您正在寻找的魔力。

// Get the current time
IClock systemClock = SystemClock.Instance;
Instant now = systemClock.Now;

// Get the local time zone, and the current date
DateTimeZone tz = DateTimeZoneProviders.Tzdb.GetSystemDefault();
LocalDate today = now.InZone(tz).Date;

// Get the start of the day, and the start of the next day as the end date
ZonedDateTime dayStart = tz.AtStartOfDay(today);
ZonedDateTime dayEnd = tz.AtStartOfDay(today.PlusDays(1));

// Compare instants using inclusive start and exclusive end
ZonedDateTime other = new ZonedDateTime(); // some other value
bool between = dayStart.ToInstant() <= other.ToInstant() &&
               dayEnd.ToInstant() > other.ToInstant();

几点:

  • 最好养成将时钟实例与对 Now 的调用分开的习惯。这使得以后在单元测试时更容易更换时钟。

  • 您只需要获取一次本地时区。我更喜欢使用Tzdb 提供程序,但任何一个提供程序都可以用于此目的。

  • 对于一天的结束,最好使用第二天的开始。这使您不必处理粒度问题,例如您是否应该采取 23:59、23:59:59、23:59.999、23:59:59.9999999 等。此外,它可以更容易地获得整数做数学的结果。

    通常,日期+时间范围(或仅时间范围)应视为半开区间[start,end) - 而仅日期范围应视为全闭区间[start,end]

  • 因此,开始与&lt;=比较,但结束与&gt;比较。

  • 如果您确定另一个 ZonedDateTime 值在同一时区并使用同一日历,则可以省略对 ToInstant 的调用,直接比较它们。

更新

正如 Jon 在 cmets 中提到的那样,Interval 类型可能会为此提供有用的便利。它已经设置为使用 Instant 值的半开范围。以下函数将获取特定时区中当前“天”的间隔:

public Interval GetTodaysInterval(IClock clock, DateTimeZone timeZone)
{
    LocalDate today = clock.Now.InZone(timeZone).Date;
    ZonedDateTime dayStart = timeZone.AtStartOfDay(today);
    ZonedDateTime dayEnd = timeZone.AtStartOfDay(today.PlusDays(1));
    return new Interval(dayStart.ToInstant(), dayEnd.ToInstant());
}

这样称呼它(使用与上面相同的值):

Interval day = GetTodaysInterval(systemClock, tz);

现在可以使用Contains 函数进行比较:

bool between = day.Contains(other.ToInstant());

请注意,您仍然需要转换为 Instant,因为 Interval 类型不支持时区。

【讨论】:

  • 它隐藏得很好;)感谢您的回答和您指出的最佳做法。
  • 您可能还想提及Interval - 例如,您可以轻松编写一个方法,该方法在给定时钟和时区(或 2.0 中的 ZonedClock .. .)
  • 使用间隔的更新示例是否不正确,因为它包括第二天的开始?
  • @GlennMorton - 这是一个半开区间。换句话说,开始是包容的,但结束是排除的。在处理时间时,这是一个非常有用的概念。 Interval 类型很好地模拟了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-25
  • 1970-01-01
  • 2012-10-30
  • 2012-02-27
  • 2016-07-02
相关资源
最近更新 更多