Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
498 views
in Technique[技术] by (71.8m points)

c# - Noda Time - Start/end of day with zone

What's the proper and more concise way to get the ZonedDateTime(s) which represent the start and the end of the current day in the timezone set on the system on which the code runs?

Isn't the following code too much complicated?

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

Given those values, I need to test if another ZonedDateTime is between them.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The AtStartOfDay value on the DateTimeZone object has the magic you're looking for.

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

A couple of points:

  • It's better to get in the habit of separating the clock instance from the call to Now. This makes it easier to replace the clock later when unit testing.

  • You only need to get the local time zone once. I prefer to use the Tzdb provider, but either provider will work for this purpose.

  • For the end of day, it's better to use the start of the next day. This prevents you from having to deal with granularity issues, such as whether you should take 23:59, 23:59:59, 23:59.999, 23:59:59.9999999, etc. Also, it makes it easier to get whole-number results when doing math.

    In general, date+time ranges (or time-only ranges) should be treated as half-open intervals [start,end) - while date-only ranges should be treated as fully-closed intervals [start,end].

  • Because of this, the start is compared with <= but the end is compared with >.

  • If you know for certain that the other ZonedDateTime value is in the same time zone and uses the same calendar, you can omit the calls to ToInstant and just compare them directly.

Update

As Jon mentioned in comments, the Interval type may be a useful convenience for this purpose. It is already set up to work with a half-open range of Instant values. The following function will get the interval for a the current "day" in a particular time zone:

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

Call it like this (using the same values from above):

Interval day = GetTodaysInterval(systemClock, tz);

And now comparison can be done with the Contains function:

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

Note that you still have to convert to an Instant, as the Interval type is not time zone aware.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...