I have a requirement to calculate the duration between two times that may span midnight.
The use case is to allow the user to set up shift plans e.g. “09:00 to 17:00” or “22:00 to 06:00” and calculate the contracted time
For “09:00 to 17:00” I can use:
LocalTime inAt = new LocalTime(9, 0);
LocalTime outAt = new LocalTime(17, 0);
var period = Period.Between(inAt, outAt);
Which results in 8 hours, the answer I am looking for.
For “22:00 to 06:00” the period returns 16 hours (regardless of the order of the parameters).
LocalTime inAt = new LocalTime(22, 0);
LocalTime outAt = new LocalTime(6, 0);
var period = Period.Between(inAt, outAt);
var period2 = Period.Between(outAt, inAt);
I am guessing that this is related to daylight saving time, unless you know the dates you cannot be sure that the answer will always the 8 hours. If the clocks go forward it would be 7, backwards would be 9.
How can I ensure that no matter what LocalTime
values are used the period would disregard any daylight savings? Should I use LocalDateTime
with an arbitrary date such as 2021-01-01?
Also, am I correct in using Period
or should I be using Duration
?
Update
This seems to work however I am still wondering if there is an eaiser way of doing it?
LocalTime inAt = new LocalTime(22, 0);
LocalTime outAt = new LocalTime(6, 0);
var period = Period.Between(inAt, outAt, PeriodUnits.Ticks);
LocalTime? midnightAdjustedTime = null;
if (period.Ticks < 0)
{
midnightAdjustedTime = LocalTime.Midnight period;
}
var duration = Duration.FromTicks(midnightAdjustedTime?.TickOfDay ?? period.Ticks);
CodePudding user response:
This has nothing to do with daylight savings - it can't do, given that everything is in terms of LocalTime
. It's about negative periods.
For “22:00 to 06:00” the period returns 16 hours (regardless of the order of the parameters)
No, it doesn't. One returns 16 hours, the other returns -16 hours:
using NodaTime;
var start = new LocalTime(22, 0);
var end = new LocalTime(6, 0);
var startToEnd = Period.Between(start, end);
var endToStart = Period.Between(end, start);
Console.WriteLine(startToEnd);
Console.WriteLine(endToStart);
Output:
PT-16H
PT16H
It's not clear to me how you came to the conclusion that both returned the same value, but the fact that they don't return the same value is crucial to fixing the problem. The simple approach is just to add 1 day (24 hours) if the period is negative. The code you've got is almost right, but it can be done much simpler - and without using the Ticks
unit at all:
// Note: Period.ToDuration() isn't "generally safe" due to variable lengths
// of months etc, but is okay here.
var duration = startToEnd.ToDuration();
if (duration < Duration.Zero)
{
duration = Duration.FromDays(1);
}
Console.WriteLine(duration);