Apologies if this seems a simple question, but I mostly just want to check that the solution I have is the most sensible/holds for all cases.
The pre-existing SQL Server database we work with stores a 'period' as inclusive start -> inclusive end UTC DateTime values. (Both start & end columns are datetime2(7)
, automatically-converted to System.DateTime
instances of DateTimeKind.UTC
before we start working with them).
So, if I need to store the "whole day/month/year, given the user's time-zone" I need to find "The last possible instant of a specified LocalDate, in a particular DateTimeZone".
The methods I have are as follows:
public static LocalDateTime AtEndOfDay(this LocalDate localDate)
{
return localDate
.PlusDays(1)
.AtMidnight()
.PlusTicks(-1);
}
public static ZonedDateTime AtEndOfDay(this DateTimeZone zone, LocalDate localDate)
{
return zone
.AtStartOfDay(localDate.PlusDays(1))
.Plus(Duration.FromTicks(-1));
}
I think I also need to avoid (anywhere else) mapping a "end of date" LocalDateTime
using .AtLeniently(..)
since if 23:59:59.9999999 is "skipped" and does not exist in the target DateTimeZone
, then it would be mapped to the next available instant on the 'outer' side of the gap 00:00:00.000000, which would give me an exclusive ZonedDateTime
value.
Amended Methods:
public static LocalDateTime AtEndOfDay(this LocalDate localDate)
{
// TODO: Replace with localDate.At(LocalTime.MaxValue) when NodaTime 2.0 is released.
return localDate
.PlusDays(1)
.AtMidnight()
.PlusTicks(-1);
}
public static ZonedDateTime AtEndOfDay(this DateTimeZone zone, LocalDate localDate)
{
return zone
.AtStartOfDay(localDate.PlusDays(1))
.Plus(-Duration.Epsilon);
}
public static ZonedDateTime AtEndOfDayInZone(this LocalDate localDate, DateTimeZone zone)
{
return zone.AtEndOfDay(localDate);
}
Firstly, as noted in comments, any time you can work with an exclusive upper bound, that would be a good idea :)
Your AtEndOfDay
method looks reasonable to me, except that I'd use Duration.Epsilon
instead of Duration.FromTicks
. In particular, in Noda Time 2.0 we're going to move to a precision of nanoseconds instead of ticks; Duration.Epsilon
will do what you want in both cases.
For your LocalDate
solution, it seems to me that we're missing a value of LocalTime.MaxValue
(or EndOfDay
), the largest representable LocalTime
. If that were available, you could just write:
return date.At(LocalTime.MaxValue);
which would remove the same "ticks" problem as before. I'll try to remember to add that for 2.0 - although it will be documented with a comment to the effect of "Only use this if you're forced to by legacy systems" :)
One downside of adding a day and then subtracting a tick (or a nanosecond) is that it will fail on LocalDate.MaxValue
. This probably isn't a practical issue, but for code within Noda Time itself, we try to avoid things like that. I wouldn't try to avoid it for the ZonedDateTime
version though, as it's a rather more complicated scenario. (There are probably ways of doing it, but it wouldn't be worth it.)
See more on this question at Stackoverflow