Noda Time - 带时区的一天开始/结束

15

如何以更简明的方式获取代表当前日期开始和结束的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。例如,在巴西的夏令时前向转换中,时间从晚上11:59到凌晨1点,没有12:00 am。在这种情况下,InZoneStrictly 会抛出异常。如果我正确地阅读文档,InZoneLeniently 在将12am的LocalDateTime转换为1am时会继续执行。同样,在向后转换时,时间从晚上11:59到晚上11点,有两个11:59 pm。InZoneLeniently 选择后面的那个。 - Mike Zboray
@mikez - 关于巴西的观点很好。我演示的方法已经涵盖了这一点。谢谢。 - Matt Johnson-Pint
1个回答

23

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();

几个要点:

  • 养成将时钟实例与现在时间调用分开的习惯更好。这样做可以更轻松地在单元测试时更换时钟。

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

  • 对于一天的结束,最好使用第二天的开始时间。这样可以避免处理粒度问题,例如是否应取23:59、23:59:59、23:59.999、23:59:59.9999999等。此外,在进行数学计算时也更容易得到整数结果。

    通常情况下,日期时间范围(或仅时间范围)应被视为半开区间 [start,end)——而日期范围应被视为完全闭合区间 [start,end]

  • 因此,起始值与 <= 进行比较,而结束值与 > 进行比较。

  • 如果您确定另一个 ZonedDateTime 值与同一时区和日历相同,则可以省略对 ToInstant 的调用,直接进行比较。

更新

如 Jon 在评论中提到的那样,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 类型不支持时区感知。

它藏得很好,就在明处 ;) 感谢您的回答和指出的最佳实践。 - Jhack
1
你可能还想提到 Interval - 例如,你可以轻松编写一个方法,根据时钟和时区(或来自2.0的ZonedClock),返回“今天”的时间间隔。 - Jon Skeet
使用Interval更新的示例是否不正确,因为它包括下一天的开始? - Glenn Morton
@GlennMorton - 这是一个半开区间。换句话说,起始点是包含的,但结束点是排除的。在处理时间时,这是一个非常有用的概念。Interval类型很好地模拟了它。 - Matt Johnson-Pint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接