Question

I'm trying to workout the amount of time between two LocalDateTime values and exclude specific dates (in this example, it's bank holidays).

var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);

var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);

The differenceBetween value shows the number of days/hours/minutes/seconds between the two dates, as you would expect.

I could check every single day from the start date and see if the bankHolidays collection contains that date e.g.

var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);

var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);

var london = DateTimeZoneProviders.Tzdb["Europe/London"];

for (var i = 1; i < differenceBetween.Days; ++i)
{
    var x = localDateTime1.InZoneStrictly(london) + Duration.FromStandardDays(i);

    if (bankHolidays.Any(date => date == x.Date))
    {
        //subtract one day for the period.
    }
}

I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?

I also need to include weekends in this exclusion too, the obvious way seems to be to check the day of the week for weekends whilst checking bank holidays, this just doesn't seem like the best/correct way of handling it though.

Was it helpful?

Solution

I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?

Well, it's relatively easy to count the number of bank holidays included in a date-to-date range:

  • Sort all the bank holidays in chronological order
  • Use a binary search to find out where the start date would come in the collection
  • Use a binary search to find out where the end date would come in the collection
  • Subtract one index from another to find how many entries are within that range
  • Work out the whole period using Period.Between as you're already doing
  • Subtract the number of entries in the range from the total number of days in the range

The fiddly bit is taking into account that the start and/or end dates may be bank holidays. There's a lot of potential for off-by-one errors, but with a good set of unit tests it should be okay.

Alternatively, if you've got relatively few bank holidays, you can just use:

var period = Period.Between(start, end,
                            PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
var holidayCount = holidays.Count(x => x >= start && x <= end);
period = period - Period.FromDays(holidayCount);

OTHER TIPS

Just use TimeSpan to get the difference, all times are in your current local time zone:

var bankHolidays = new[] { new DateTime(2013, 12, 25), new DateTime(2013, 12, 26) };
var localDateTime1 = new DateTime(2013, 11, 18, 10, 30, 0);
var localDateTime2 = new DateTime(2013, 12, 29, 10, 15, 0);
var span = localDateTime2 - localDateTime1;
var holidays = bankHolidays[1] - bankHolidays[0];
var duration = span-holidays;

Now duration is your time elapsed between localDateTime1 and localDateTime2. If you want to exlude two dates via the bankHolidays you can easiely modify the operations above.

You might use an extra method for this operation:

public static TimeSpan GetPeriod(DateTime start, DateTime end, params DateTime[] exclude)
{
    var span = end - start;
    if (exclude == null) return span;
    span = exclude.Where(d => d >= start && d <= end)
    .Aggregate(span, (current, date) => current.Subtract(new TimeSpan(1, 0, 0, 0)));
    return span;
}

Now you can just use this:

var duration = GetPeriod(localDateTime1, localDateTime2, bankHolidays);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top